google-site-verification: google61fe8ba583a51912.html
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