Kibana Dashboard for Java Applications: Complete Implementation Guide

Kibana is a powerful data visualization tool that works seamlessly with Elasticsearch to provide real-time insights into your Java applications. This article covers everything from logging setup and data ingestion to creating comprehensive Kibana dashboards for monitoring Java applications.


Architecture Overview

Java Application → Logback/Log4j → Elasticsearch → Kibana Dashboard
↓
Metrics/APM Data → Beats → Elasticsearch

1. Java Application Logging Setup

1.1 Maven Dependencies

<!-- Elasticsearch REST Client -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.0</version>
</dependency>
<!-- Logback with Elasticsearch Appender -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
<!-- Logstash Logback Encoder -->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.2</version>
</dependency>
<!-- Micrometer for Metrics -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-elastic</artifactId>
<version>1.9.0</version>
</dependency>

1.2 Structured Logging Configuration

logback-spring.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- Console Appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp/>
<logLevel/>
<loggerName/>
<message/>
<mdc/>
<stackTrace/>
<threadName/>
<pattern>
<pattern>
{
"service": "order-service",
"environment": "${SPRING_PROFILES_ACTIVE:-development}",
"version": "1.0.0"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<!-- Elasticsearch Appender -->
<appender name="ELASTICSEARCH" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.json</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/application.%d{yyyy-MM-dd}.json</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>UTC</timeZone>
</timestamp>
<logLevel/>
<loggerName/>
<message/>
<mdc/>
<stackTrace/>
<threadName/>
<context/>
<pattern>
<pattern>
{
"service": "order-service",
"environment": "${SPRING_PROFILES_ACTIVE:-development}",
"version": "1.0.0",
"host": "${HOSTNAME:-localhost}"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<!-- HTTP Appender (Direct to Elasticsearch) -->
<appender name="ELASTIC_HTTP" class="com.internetitem.logback.elasticsearch.ElasticsearchAppender">
<url>http://localhost:9200/_bulk</url>
<index>java-app-logs-%date{yyyy-MM-dd}</index>
<type>_doc</type>
<connectTimeout>30000</connectTimeout>
<errorsToStderr>true</errorsToStderr>
<includeCallerData>true</includeCallerData>
<logsToStderr>true</logsToStderr>
<maxQueueSize>104857600</maxQueueSize>
<readTimeout>30000</readTimeout>
<sleepTime>250</sleepTime>
<maxRetries>3</maxRetries>
<includeMdc>true</includeMdc>
<authentication class="com.internetitem.logback.elasticsearch.config.BasicAuthentication">
<user>elastic</user>
<password>password</password>
</authentication>
</appender>
<!-- Root Logger -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ELASTICSEARCH"/>
<appender-ref ref="ELASTIC_HTTP"/>
</root>
<!-- Application-specific Logger -->
<logger name="com.example.orderservice" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ELASTICSEARCH"/>
</logger>
</configuration>

1.3 Structured Logging in Java Code

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
@Component
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
public Order processOrder(OrderRequest request) {
// Set context information for logging
MDC.put("orderId", request.getOrderId());
MDC.put("customerId", request.getCustomerId());
MDC.put("transactionId", generateTransactionId());
try {
logger.info("Processing order", 
kv("amount", request.getAmount()),
kv("currency", request.getCurrency()),
kv("items", request.getItemCount()));
// Business logic
validateOrder(request);
Order order = createOrder(request);
logger.info("Order processed successfully",
kv("orderStatus", order.getStatus()),
kv("processingTime", System.currentTimeMillis() - startTime));
return order;
} catch (Exception e) {
logger.error("Failed to process order",
kv("errorMessage", e.getMessage()),
kv("errorType", e.getClass().getSimpleName()));
throw e;
} finally {
MDC.clear();
}
}
// Helper method for structured logging
private static Object kv(String key, Object value) {
return new Object() {
@Override
public String toString() {
return key + "=" + value;
}
};
}
}

2. Metrics Collection with Micrometer

2.1 Elasticsearch Metrics Configuration

@Configuration
public class MetricsConfig {
@Value("${elasticsearch.host:localhost}")
private String elasticsearchHost;
@Value("${elasticsearch.port:9200}")
private int elasticsearchPort;
@Bean
public MeterRegistry meterRegistry() {
ElasticConfig config = new ElasticConfig() {
@Override
public String host() {
return elasticsearchHost;
}
@Override
public int port() {
return elasticsearchPort;
}
@Override
public String step() {
return "1m"; // Send metrics every minute
}
@Override
public String index() {
return "java-app-metrics";
}
@Override
public String get(String key) {
return null;
}
};
return new ElasticMeterRegistry(config, Clock.SYSTEM);
}
}

2.2 Custom Metrics Collection

@Service
public class ApplicationMetrics {
private final MeterRegistry meterRegistry;
private final Counter orderCounter;
private final Timer orderProcessingTimer;
private final Gauge memoryUsage;
private final DistributionSummary orderAmountSummary;
public ApplicationMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
// Counter for tracking order counts
this.orderCounter = Counter.builder("orders.total")
.description("Total number of orders processed")
.tag("service", "order-service")
.register(meterRegistry);
// Timer for tracking order processing duration
this.orderProcessingTimer = Timer.builder("orders.processing.time")
.description("Order processing time")
.tag("service", "order-service")
.register(meterRegistry);
// Gauge for memory usage
this.memoryUsage = Gauge.builder("jvm.memory.used")
.description("JVM memory used")
.tag("service", "order-service")
.register(meterRegistry, this, 
metrics -> Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
// Distribution summary for order amounts
this.orderAmountSummary = DistributionSummary.builder("orders.amount")
.description("Order amount distribution")
.baseUnit("USD")
.tag("service", "order-service")
.register(meterRegistry);
}
public void recordOrder(Order order, long processingTime) {
orderCounter.increment();
orderProcessingTimer.record(processingTime, TimeUnit.MILLISECONDS);
orderAmountSummary.record(order.getAmount());
// Record custom business metrics
meterRegistry.counter("orders.by_status", 
"status", order.getStatus().name(),
"customer_tier", order.getCustomerTier())
.increment();
}
public void recordError(String errorType, String context) {
meterRegistry.counter("application.errors",
"error_type", errorType,
"context", context,
"service", "order-service")
.increment();
}
}

2.3 HTTP Request Metrics

@Component
public class RequestMetricsFilter implements Filter {
private final MeterRegistry meterRegistry;
public RequestMetricsFilter(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, 
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
long startTime = System.currentTimeMillis();
try {
chain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startTime;
Timer.builder("http.requests")
.tag("method", httpRequest.getMethod())
.tag("uri", httpRequest.getRequestURI())
.tag("status", String.valueOf(httpResponse.getStatus()))
.tag("service", "order-service")
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);
// Count requests by status code
Counter.builder("http.requests.count")
.tag("method", httpRequest.getMethod())
.tag("status", String.valueOf(httpResponse.getStatus()))
.tag("service", "order-service")
.register(meterRegistry)
.increment();
}
}
}

3. Elasticsearch Client Integration

3.1 Elasticsearch Configuration

@Configuration
public class ElasticsearchConfig {
@Value("${elasticsearch.hosts:localhost:9200}")
private String[] hosts;
@Bean
public RestHighLevelClient elasticsearchClient() {
HttpHost[] httpHosts = Arrays.stream(hosts)
.map(host -> {
String[] parts = host.split(":");
return new HttpHost(parts[0], Integer.parseInt(parts[1]), "http");
})
.toArray(HttpHost[]::new);
return new RestHighLevelClient(
RestClient.builder(httpHosts)
.setRequestConfigCallback(requestConfigBuilder ->
requestConfigBuilder
.setConnectTimeout(5000)
.setSocketTimeout(60000))
.setHttpClientConfigCallback(httpClientBuilder ->
httpClientBuilder
.setMaxConnTotal(100)
.setMaxConnPerRoute(50))
);
}
}

3.2 Custom Elasticsearch Indexing

@Service
public class ElasticsearchService {
private final RestHighLevelClient elasticsearchClient;
private final ObjectMapper objectMapper;
public ElasticsearchService(RestHighLevelClient elasticsearchClient, 
ObjectMapper objectMapper) {
this.elasticsearchClient = elasticsearchClient;
this.objectMapper = objectMapper;
}
public void indexBusinessEvent(String eventType, Object data) {
try {
Map<String, Object> document = new HashMap<>();
document.put("timestamp", new Date());
document.put("eventType", eventType);
document.put("service", "order-service");
document.put("environment", getEnvironment());
document.put("data", data);
IndexRequest request = new IndexRequest("business-events")
.source(document)
.opType(DocWriteRequest.OpType.CREATE);
elasticsearchClient.index(request, RequestOptions.DEFAULT);
} catch (Exception e) {
logger.error("Failed to index business event", e);
}
}
public void indexPerformanceMetric(String metricName, double value, 
Map<String, String> tags) {
try {
Map<String, Object> document = new HashMap<>();
document.put("@timestamp", new Date());
document.put("metricName", metricName);
document.put("value", value);
document.put("service", "order-service");
document.putAll(tags);
IndexRequest request = new IndexRequest("performance-metrics")
.source(document);
elasticsearchClient.index(request, RequestOptions.DEFAULT);
} catch (Exception e) {
logger.error("Failed to index performance metric", e);
}
}
public List<Map<String, Object>> searchLogs(String query, 
LocalDateTime from, 
LocalDateTime to) {
try {
SearchRequest searchRequest = new SearchRequest("java-app-logs-*");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.boolQuery()
.must(QueryBuilders.queryStringQuery(query))
.filter(QueryBuilders.rangeQuery("@timestamp")
.gte(from.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())
.lte(to.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()))
);
sourceBuilder.size(1000);
searchRequest.source(sourceBuilder);
SearchResponse response = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
return Arrays.stream(response.getHits().getHits())
.map(hit -> {
try {
return objectMapper.readValue(hit.getSourceAsString(), Map.class);
} catch (Exception e) {
return Collections.<String, Object>singletonMap("error", e.getMessage());
}
})
.collect(Collectors.toList());
} catch (Exception e) {
logger.error("Failed to search logs", e);
return Collections.emptyList();
}
}
}

4. Kibana Dashboard Configuration

4.1 Index Patterns Setup

Create index patterns in Kibana for:

  • java-app-logs-* - Application logs
  • java-app-metrics - Application metrics
  • business-events - Custom business events
  • performance-metrics - Performance metrics

4.2 Kibana Dashboard JSON Configuration

Java Application Monitoring Dashboard:

{
"title": "Java Application Monitoring",
"description": "Comprehensive monitoring for Java applications",
"panels": [
{
"id": "application-metrics",
"type": "visualization",
"panelIndex": 1,
"gridData": {
"x": 0,
"y": 0,
"w": 24,
"h": 3
}
}
],
"options": {
"darkTheme": false,
"hidePanelTitles": false,
"useMargins": true
},
"timeRestore": true,
"kibanaSavedObjectMeta": {
"searchSourceJSON": "{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"
}
}

4.3 Visualization Configurations

1. Application Throughput Visualization:

{
"title": "HTTP Requests per Minute",
"visState": {
"title": "HTTP Requests per Minute",
"type": "line",
"params": {
"type": "line",
"grid": {
"categoryLines": false,
"style": {
"color": "#eee"
}
},
"categoryAxes": [
{
"id": "CategoryAxis-1",
"type": "category",
"position": "bottom",
"show": true,
"style": {},
"scale": {
"type": "linear"
},
"labels": {
"show": true,
"truncate": 100
}
}
],
"valueAxes": [
{
"id": "ValueAxis-1",
"name": "LeftAxis-1",
"type": "value",
"position": "left",
"show": true,
"style": {},
"scale": {
"type": "linear",
"mode": "normal"
},
"labels": {
"show": true,
"rotate": 0,
"filter": false,
"truncate": 100
}
}
],
"seriesParams": [
{
"show": true,
"type": "line",
"mode": "normal",
"data": {
"label": "Requests",
"id": "1"
},
"valueAxis": "ValueAxis-1",
"drawLinesBetweenPoints": true,
"showCircles": true,
"interpolate": "linear"
}
],
"addTooltip": true,
"addLegend": true,
"legendPosition": "right"
},
"aggs": [
{
"id": "1",
"enabled": true,
"type": "count",
"schema": "metric",
"params": {}
},
{
"id": "2",
"enabled": true,
"type": "date_histogram",
"schema": "segment",
"params": {
"field": "@timestamp",
"interval": "auto",
"customInterval": "2h",
"min_doc_count": 1,
"extended_bounds": {}
}
},
{
"id": "3",
"enabled": true,
"type": "terms",
"schema": "group",
"params": {
"field": "http.status_code",
"size": 5,
"order": "desc",
"orderBy": "1"
}
}
]
}
}

2. Error Rate Dashboard:

{
"title": "Application Error Rate",
"visState": {
"title": "Application Error Rate",
"type": "metric",
"params": {
"addLegend": false,
"addTooltip": true,
"gauge": {
"verticalSplit": false,
"extendRange": true,
"percentageMode": false,
"gaugeType": "Metric",
"gaugeStyle": "Full",
"backStyle": "Full",
"orientation": "vertical",
"colorSchema": "Green to Red",
"gaugeColorMode": "Labels",
"useRange": false,
"colorsRange": [
{
"from": 0,
"to": 50
},
{
"from": 50,
"to": 75
},
{
"from": 75,
"to": 100
}
],
"invertColors": false,
"labels": {
"show": true,
"color": "black"
},
"scale": {
"show": false,
"labels": false,
"color": "#333"
},
"type": "simple",
"style": {
"fontSize": "20px",
"bgColor": false,
"bgFill": "#000",
"labelColor": true,
"subText": "Errors per minute"
}
},
"type": "gauge"
},
"aggs": [
{
"id": "1",
"enabled": true,
"type": "count",
"schema": "metric",
"params": {
"customLabel": "Error Count"
}
}
]
}
}

5. Java Kibana Dashboard Client

5.1 Kibana REST API Client

@Service
public class KibanaDashboardClient {
private final RestTemplate restTemplate;
private final String kibanaUrl;
private final String username;
private final String password;
public KibanaDashboardClient(@Value("${kibana.url:http://localhost:5601}") String kibanaUrl,
@Value("${kibana.username:elastic}") String username,
@Value("${kibana.password:password}") String password) {
this.kibanaUrl = kibanaUrl;
this.username = username;
this.password = password;
this.restTemplate = createRestTemplate();
}
private RestTemplate createRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
// Add basic authentication
restTemplate.getInterceptors().add((request, body, execution) -> {
String auth = username + ":" + password;
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
request.getHeaders().add("Authorization", "Basic " + encodedAuth);
request.getHeaders().add("kbn-xsrf", "true");
request.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return execution.execute(request, body);
});
return restTemplate;
}
public void createDashboard(String dashboardId, String dashboardJson) {
try {
String url = kibanaUrl + "/api/saved_objects/dashboard/" + dashboardId;
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("attributes", objectMapper.readValue(dashboardJson, Map.class));
HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody);
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.POST, request, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
logger.info("Dashboard created successfully: {}", dashboardId);
} else {
logger.error("Failed to create dashboard: {}", response.getBody());
}
} catch (Exception e) {
logger.error("Error creating Kibana dashboard", e);
}
}
public String getDashboardUrl(String dashboardId) {
return kibanaUrl + "/app/dashboards#/view/" + dashboardId;
}
public List<Map<String, Object>> getDashboardData(String indexPattern, 
String query, 
LocalDateTime from, 
LocalDateTime to) {
try {
String url = kibanaUrl + "/api/data/search/es";
Map<String, Object> searchRequest = new HashMap<>();
searchRequest.put("params", Map.of(
"index", indexPattern,
"body", Map.of(
"query", Map.of(
"bool", Map.of(
"must", List.of(
Map.of("query_string", Map.of("query", query))
),
"filter", List.of(
Map.of("range", Map.of(
"@timestamp", Map.of(
"gte", from.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
"lte", to.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
)
))
)
)
),
"size", 1000
)
));
HttpEntity<Map<String, Object>> request = new HttpEntity<>(searchRequest);
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.POST, request, Map.class);
return extractHits(response.getBody());
} catch (Exception e) {
logger.error("Error fetching dashboard data", e);
return Collections.emptyList();
}
}
@SuppressWarnings("unchecked")
private List<Map<String, Object>> extractHits(Map<String, Object> response) {
try {
Map<String, Object> rawResponse = (Map<String, Object>) response.get("rawResponse");
Map<String, Object> hits = (Map<String, Object>) rawResponse.get("hits");
List<Map<String, Object>> hitList = (List<Map<String, Object>>) hits.get("hits");
return hitList.stream()
.map(hit -> (Map<String, Object>) hit.get("_source"))
.collect(Collectors.toList());
} catch (Exception e) {
logger.error("Error extracting hits from response", e);
return Collections.emptyList();
}
}
}

5.2 Automated Dashboard Management

@Component
public class DashboardManager {
private final KibanaDashboardClient kibanaClient;
private final ObjectMapper objectMapper;
public DashboardManager(KibanaDashboardClient kibanaClient, ObjectMapper objectMapper) {
this.kibanaClient = kibanaClient;
this.objectMapper = objectMapper;
}
@PostConstruct
public void initializeDashboards() {
createApplicationOverviewDashboard();
createErrorAnalysisDashboard();
createPerformanceDashboard();
createBusinessMetricsDashboard();
}
private void createApplicationOverviewDashboard() {
try {
String dashboardJson = """
{
"title": "Java Application Overview",
"hits": 0,
"description": "Overview of application health and performance",
"panelsJSON": "[...]",
"optionsJSON": "{\\"darkTheme\\":false}",
"version": 1,
"timeRestore": true,
"kibanaSavedObjectMeta": {
"searchSourceJSON": "{\\"query\\":{\\"query\\":\\"\\",\\"language\\":\\"kuery\\"},\\"filter\\":[]}"
}
}
""";
kibanaClient.createDashboard("java-app-overview", dashboardJson);
} catch (Exception e) {
logger.error("Failed to create application overview dashboard", e);
}
}
public Map<String, Object> getApplicationHealthSummary() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime oneHourAgo = now.minusHours(1);
List<Map<String, Object>> errors = kibanaClient.getDashboardData(
"java-app-logs-*", "level:ERROR", oneHourAgo, now);
List<Map<String, Object>> requests = kibanaClient.getDashboardData(
"java-app-metrics", "metricName:http.requests.count", oneHourAgo, now);
return Map.of(
"errorCount", errors.size(),
"requestCount", requests.stream()
.mapToInt(req -> ((Number) req.get("value")).intValue())
.sum(),
"averageResponseTime", calculateAverageResponseTime(oneHourAgo, now),
"serviceStatus", calculateServiceStatus(errors, requests)
);
}
private String calculateServiceStatus(List<Map<String, Object>> errors, 
List<Map<String, Object>> requests) {
int totalRequests = requests.stream()
.mapToInt(req -> ((Number) req.get("value")).intValue())
.sum();
double errorRate = totalRequests > 0 ? (double) errors.size() / totalRequests : 0;
if (errorRate > 0.1) return "CRITICAL";
if (errorRate > 0.05) return "WARNING";
return "HEALTHY";
}
private double calculateAverageResponseTime(LocalDateTime from, LocalDateTime to) {
List<Map<String, Object>> responseTimes = kibanaClient.getDashboardData(
"java-app-metrics", "metricName:http.requests.duration", from, to);
return responseTimes.stream()
.mapToDouble(rt -> ((Number) rt.get("value")).doubleValue())
.average()
.orElse(0.0);
}
}

6. Spring Boot Actuator Integration

6.1 Actuator Configuration

# application.yml
management:
endpoints:
web:
exposure:
include: health,metrics,loggers,info,prometheus
endpoint:
health:
show-details: always
metrics:
enabled: true
metrics:
export:
elastic:
enabled: true
host: localhost
port: 9200
step: 1m
distribution:
percentiles-histogram:
http.server.requests: true
percentiles:
http.server.requests: 0.5, 0.95, 0.99
elastic:
metrics:
export:
index: application-metrics
index-date-format: yyyy-MM-dd

6.2 Custom Health Indicator

@Component
public class KibanaConnectivityHealthIndicator implements HealthIndicator {
private final KibanaDashboardClient kibanaClient;
public KibanaConnectivityHealthIndicator(KibanaDashboardClient kibanaClient) {
this.kibanaClient = kibanaClient;
}
@Override
public Health health() {
try {
// Try to get dashboard URL as connectivity test
String url = kibanaClient.getDashboardUrl("test");
return Health.up()
.withDetail("kibanaUrl", url)
.withDetail("status", "connected")
.build();
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.withDetail("status", "disconnected")
.build();
}
}
}

7. Alerting and Notifications

7.1 Kibana Alert Rules

@Service
public class AlertManager {
private final KibanaDashboardClient kibanaClient;
private final ApplicationMetrics metrics;
public AlertManager(KibanaDashboardClient kibanaClient, ApplicationMetrics metrics) {
this.kibanaClient = kibanaClient;
this.metrics = metrics;
}
public void checkAndAlert() {
checkErrorRate();
checkResponseTime();
checkSystemResources();
}
private void checkErrorRate() {
Map<String, Object> healthSummary = getHealthSummary();
double errorRate = (double) healthSummary.get("errorRate");
if (errorRate > 0.05) { // 5% error rate threshold
sendAlert("HIGH_ERROR_RATE", 
String.format("Error rate is %.2f%%", errorRate * 100),
"CRITICAL");
}
}
private void checkResponseTime() {
double avgResponseTime = getAverageResponseTime();
if (avgResponseTime > 1000) { // 1 second threshold
sendAlert("HIGH_RESPONSE_TIME",
String.format("Average response time is %.2f ms", avgResponseTime),
"WARNING");
}
}
private void sendAlert(String alertType, String message, String severity) {
// Log alert
logger.warn("ALERT: {} - {} - {}", severity, alertType, message);
// Record metric
metrics.recordError(alertType, message);
// Could integrate with external alerting systems (PagerDuty, Slack, etc.)
// sendToSlack(alertType, message, severity);
// sendToPagerDuty(alertType, message, severity);
}
}

8. Best Practices

8.1 Performance Optimization

@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public TaskExecutor logTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(1000);
executor.setThreadNamePrefix("kibana-logger-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
@Service
public class AsyncLogService {
private final TaskExecutor taskExecutor;
private final ElasticsearchService elasticsearchService;
public AsyncLogService(TaskExecutor taskExecutor, ElasticsearchService elasticsearchService) {
this.taskExecutor = taskExecutor;
this.elasticsearchService = elasticsearchService;
}
@Async("logTaskExecutor")
public void logAsync(String eventType, Object data) {
elasticsearchService.indexBusinessEvent(eventType, data);
}
}

8.2 Security Considerations

@Configuration
public class SecurityConfig {
@Bean
public Filter loggingFilter() {
return new RequestLoggingFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request, 
HttpServletResponse response, 
FilterChain filterChain) throws ServletException, IOException {
// Remove sensitive headers before logging
HttpServletRequest wrappedRequest = new HeaderRemovingRequestWrapper(request);
super.doFilterInternal(wrappedRequest, response, filterChain);
}
};
}
private static class HeaderRemovingRequestWrapper extends HttpServletRequestWrapper {
private static final Set<String> SENSITIVE_HEADERS = Set.of(
"authorization", "cookie", "x-api-key", "password"
);
public HeaderRemovingRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getHeader(String name) {
if (SENSITIVE_HEADERS.contains(name.toLowerCase())) {
return "[REDACTED]";
}
return super.getHeader(name);
}
@Override
public Enumeration<String> getHeaderNames() {
return Collections.enumeration(
Collections.list(super.getHeaderNames()).stream()
.filter(name -> !SENSITIVE_HEADERS.contains(name.toLowerCase()))
.collect(Collectors.toList())
);
}
}
}

Conclusion

Building a comprehensive Kibana dashboard for Java applications involves:

  1. Structured Logging: Using Logback with Elasticsearch appenders
  2. Metrics Collection: Implementing Micrometer for application metrics
  3. Elasticsearch Integration: Direct indexing of business events and performance data
  4. Kibana Dashboards: Creating visualizations for monitoring and alerting
  5. Automation: Programmatic dashboard management and health checks

Key Benefits:

  • Real-time application monitoring and debugging
  • Business metrics correlation with technical performance
  • Proactive alerting on performance degradation
  • Historical analysis and trend identification
  • Centralized observability across microservices

By implementing this comprehensive approach, you can achieve full-stack observability for your Java applications, enabling faster troubleshooting, better performance optimization, and improved operational efficiency.

Leave a Reply

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


Macro Nepal Helper