Chat
Search
Ithy Logo

Implementing Prometheus in a Plain Spring 3 and Java 8 Application

A Comprehensive Guide to Monitoring Your Legacy Spring 3 Application with Prometheus

server room monitoring setup

Key Takeaways

  • Manual Integration: Prometheus can be integrated into Spring 3 applications using Prometheus Java client libraries.
  • Custom Metrics Exposure: Metrics are exposed via custom endpoints, typically through Spring MVC controllers or servlets.
  • Alternative Libraries: If preferred, Dropwizard Metrics can be utilized as an alternative for instrumentation.

Introduction

Integrating Prometheus into a legacy Spring 3 application running on Java 8 presents unique challenges due to the absence of built-in support found in newer Spring versions and frameworks like Spring Boot. Despite these challenges, it is entirely feasible to implement comprehensive monitoring by manually integrating Prometheus's Java client libraries. This guide provides a step-by-step approach to achieving this, ensuring your application is effectively monitored and metrics are accurately captured and exposed.

Prerequisites

Ensure Compatibility

Before proceeding, verify that your development environment meets the necessary requirements:

  • Java 8 is installed and configured.
  • Spring Framework 3 is properly set up in your project.
  • Maven or Gradle is used for dependency management.

Step 1: Adding Prometheus Client Dependencies

Using Maven

To start, add the Prometheus client dependencies to your project's pom.xml. Ensure the versions are compatible with Java 8.

<dependencies>
    <dependency>
        <groupId>io.prometheus</groupId>
        <artifactId>simpleclient</artifactId>
        <version>0.16.0</version>
    </dependency>
    <dependency>
        <groupId>io.prometheus</groupId>
        <artifactId>simpleclient_httpserver</artifactId>
        <version>0.16.0</version>
    </dependency>
</dependencies>

Using Gradle

If you prefer Gradle, add the following to your build.gradle:

dependencies {
    implementation 'io.prometheus:simpleclient:0.16.0'
    implementation 'io.prometheus:simpleclient_httpserver:0.16.0'
}

Adjust the version numbers as necessary, ensuring compatibility with your Java 8 environment.

Step 2: Defining and Creating Metrics

Understanding Prometheus Metrics

Prometheus supports various types of metrics, including counters, gauges, histograms, and summaries. For a Spring 3 application, counters and gauges are most commonly used to track request counts and application state respectively.

Creating a Counter

A counter is useful for tracking the number of times an event occurs, such as HTTP requests.

import io.prometheus.client.Counter;

public class MyMetrics {
    public static final Counter requests = Counter.build()
        .name("my_requests_total")
        .help("Total requests.")
        .register();
}

This code defines a static counter named my_requests_total which will be used to count the total number of requests.

Creating a Gauge

A gauge is suitable for tracking values that can go up and down, such as the number of active sessions.

import io.prometheus.client.Gauge;

public class MyMetrics {
    public static final Gauge activeSessions = Gauge.build()
        .name("active_sessions")
        .help("Number of active sessions.")
        .register();
}

This gauge will help monitor the number of active sessions in real-time.

Step 3: Instrumenting Your Application Code

Incrementing Counters

To track specific events, you need to increment the corresponding metrics within your application logic.

import io.prometheus.client.Counter;

public class MyService {
    public void processRequest() {
        // Your request processing logic
        MyMetrics.requests.inc();
    }
}

Every time the processRequest method is invoked, the requests counter increments, tracking the total number of requests processed.

Updating Gauges

import io.prometheus.client.Gauge;

public class SessionService {
    public void sessionCreated() {
        MyMetrics.activeSessions.inc();
    }

    public void sessionDestroyed() {
        MyMetrics.activeSessions.dec();
    }
}

These methods ensure that the activeSessions gauge accurately reflects the current number of active sessions by incrementing on session creation and decrementing on session destruction.

Step 4: Exposing Metrics via an HTTP Endpoint

Setting Up a Metrics Endpoint

Prometheus scrapes metrics by sending HTTP requests to a designated endpoint. You need to create this endpoint manually in your Spring 3 application.

Creating a Spring MVC Controller

import io.prometheus.client.exporter.common.TextFormat;
import io.prometheus.client.CollectorRegistry;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;

@Controller
public class PrometheusMetricsController {
    @RequestMapping("/metrics")
    public void metrics(HttpServletResponse response) throws IOException {
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType(TextFormat.CONTENT_TYPE_004);
        Writer writer = response.getWriter();
        TextFormat.write004(writer, CollectorRegistry.defaultRegistry.metricFamilySamples());
        writer.close();
    }
}

This controller maps the /metrics URL to an endpoint that outputs all registered metrics in a format that Prometheus understands.

Alternative: Using a Servlet

If you prefer using a servlet instead of a Spring MVC controller, here's how:

import io.prometheus.client.exporter.common.TextFormat;
import io.prometheus.client.CollectorRegistry;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;

public class MetricsServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(HttpServletResponse.SC_OK);
        resp.setContentType(TextFormat.CONTENT_TYPE_004);
        Writer writer = resp.getWriter();
        TextFormat.write004(writer, CollectorRegistry.defaultRegistry.metricFamilySamples());
        writer.close();
    }
}

Register this servlet in your web.xml to serve metrics at a specific path.

Step 5: Configuring Prometheus to Scrape Metrics

Updating Prometheus Configuration

To enable Prometheus to collect metrics from your application, you need to add a scrape configuration in the prometheus.yml file.

scrape_configs:
  - job_name: 'spring3-app'
    scrape_interval: 10s
    metrics_path: /metrics
    static_configs:
      - targets: ['localhost:8080']

Replace localhost:8080 with the actual host and port where your application is running.

Starting Prometheus

After updating the configuration, start or restart your Prometheus server to apply the changes. Verify that Prometheus is successfully scraping the /metrics endpoint by checking the Prometheus dashboard under the "Targets" section.

Step 6: Enhancing Metrics with Additional Collectors

Including JVM Metrics

To gain deeper insights into your application's performance, include JVM metrics such as memory usage, garbage collection, and thread counts.

<dependency>
    <groupId>io.prometheus</groupId>
    <artifactId>simpleclient_hotspot</artifactId>
    <version>0.16.0</version>
</dependency>

This dependency automatically collects and exposes various JVM metrics. Ensure it is added to your project's dependencies.

Exposing Built-in JVM Metrics

With the simpleclient_hotspot dependency added, update your metrics endpoint to include these metrics:

import io.prometheus.client.hotspot.DefaultExports;

public class ApplicationInitializer {
    public void init() {
        // Initialize default JVM metrics
        DefaultExports.initialize();
        // Start your metrics server or other initialization tasks
    }
}

Call the init method during your application's startup process to ensure JVM metrics are registered and exposed.

Alternative Approach: Using Dropwizard Metrics

Introduction to Dropwizard Metrics

Dropwizard Metrics, also known as Codahale Metrics, is a robust library for capturing application metrics. It can be integrated with Prometheus through a dedicated exporter, providing an alternative to using Prometheus's native Java client.

Adding Dropwizard Dependencies

<dependencies>
    <dependency>
        <groupId>io.dropwizard.metrics</groupId>
        <artifactId>metrics-core</artifactId>
        <version>4.1.2</version>
    </dependency>
    <dependency>
        <groupId>io.prometheus</groupId>
        <artifactId>simpleclient_dropwizard</artifactId>
        <version>0.16.0</version>
    </dependency>
</dependencies>

Add these dependencies to enable Dropwizard Metrics and the Prometheus Dropwizard exporter.

Instrumenting with Dropwizard Metrics

import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;

public class MetricsService {
    private static final MetricRegistry registry = new MetricRegistry();
    private static final Meter requests = registry.meter("requests");

    public void processRequest() {
        // Your processing logic
        requests.mark();
    }

    public MetricRegistry getRegistry() {
        return registry;
    }
}

This setup uses a meter to track the rate of incoming requests.

Integrating Prometheus with Dropwizard Metrics

import io.prometheus.client.dropwizard.DropwizardExports;
import io.prometheus.client.exporter.HTTPServer;

public class PrometheusIntegration {
    public static void main(String[] args) throws Exception {
        MetricRegistry registry = MetricsService.getRegistry();
        // Register Dropwizard metrics with Prometheus
        CollectorRegistry.defaultRegistry.register(new DropwizardExports(registry));
        // Start Prometheus HTTP server
        HTTPServer server = new HTTPServer(1234);
    }
}

This code registers Dropwizard metrics with Prometheus and starts an HTTP server on port 1234 to expose the metrics.

Advanced Configuration and Custom Metrics

Defining Custom Metrics

Beyond basic counters and gauges, you might need to define custom metrics tailored to your application's specific needs. Prometheus's client library allows for creating custom metric types and labels to provide granular insights.

import io.prometheus.client.Gauge;

public class CustomMetrics {
    public static final Gauge cacheHitRate = Gauge.build()
        .name("cache_hit_rate")
        .help("Cache hit rate.")
        .labelNames("cache_name")
        .register();

    public void recordCacheHit(String cacheName) {
        cacheHitRate.labels(cacheName).inc();
    }

    public void recordCacheMiss(String cacheName) {
        cacheHitRate.labels(cacheName).dec();
    }
}

This example defines a gauge with a cache_name label, allowing you to track the hit rate for different caches individually.

Using Histograms and Summaries

Histograms and summaries are useful for tracking distributions and quantiles of metrics like request durations.

import io.prometheus.client.Histogram;

public class RequestMetrics {
    static final Histogram requestLatency = Histogram.build()
        .name("request_latency_seconds")
        .help("Request latency in seconds.")
        .register();

    public void handleRequest() {
        Histogram.Timer timer = requestLatency.startTimer();
        try {
            // Handle the request
        } finally {
            timer.observeDuration();
        }
    }
}

This histogram measures the latency of handling requests, allowing you to analyze the distribution of request durations.

Best Practices for Prometheus Integration

Consistent Metric Naming

Use a consistent naming convention for your metrics to ensure clarity and ease of aggregation. A typical convention includes a namespace, subsystem, and metric name, such as app_requests_total.

Label Usage

Labels provide additional dimensions to your metrics. Use them judiciously to avoid high cardinality, which can lead to performance issues. Common labels include method, status, and endpoint.

Avoiding Metric Explosion

Be cautious with dynamically generated labels or metrics to prevent an explosion of unique time series. Limit the number of unique label values and avoid including high-cardinality data.

Securing the Metrics Endpoint

Ensure that your metrics endpoint is secured, especially in production environments. Restrict access to authorized users or systems to prevent unauthorized access and potential information leakage.

Monitoring and Visualization

Setting Up Grafana Dashboards

Grafana is a powerful tool for visualizing Prometheus metrics. After ensuring Prometheus is scraping your application's metrics, you can create dashboards in Grafana to visualize key metrics.

Creating a Dashboard

  1. Log in to your Grafana instance.
  2. Click on the "+" icon and select "Dashboard."
  3. Add a new panel and select Prometheus as the data source.
  4. Write PromQL queries to fetch the desired metrics.
  5. Customize the visualization type and configuration as needed.

Alerting with Prometheus

Set up alerting rules in Prometheus to notify you when certain conditions are met, such as high error rates or unusual latency.

groups:
  - name: spring3-app-alerts
    rules:
      - alert: HighRequestLatency
        expr: request_latency_seconds_sum / request_latency_seconds_count > 0.5
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High request latency detected"
          description: "The average request latency is above 0.5 seconds for the last 5 minutes."

This rule triggers an alert if the average request latency exceeds 0.5 seconds for more than five minutes.

Performance Considerations

Impact on Application Performance

Instrumenting your application with Prometheus metrics introduces some overhead. To minimize performance impacts:

  • Optimize metric collection logic to be as efficient as possible.
  • Avoid excessive use of high-cardinality labels.
  • Regularly monitor the performance of your metrics collection system.

Resource Management

Ensure that the additional resources required for metrics collection and exposition do not adversely affect your application's performance. This includes memory usage for storing metrics and CPU cycles for processing metric updates.

Conclusion

Integrating Prometheus into a plain Spring 3 and Java 8 application requires a hands-on approach due to the lack of built-in support found in newer frameworks. By manually adding Prometheus client dependencies, defining and instrumenting metrics, exposing these metrics via a custom endpoint, and configuring Prometheus to scrape these metrics, you can establish a robust monitoring system for your legacy application. Additionally, leveraging alternative libraries like Dropwizard Metrics and adhering to best practices ensures that your monitoring solution is both effective and efficient. Properly implemented, this integration will provide valuable insights into your application's performance, aiding in maintenance, troubleshooting, and optimization efforts.

References


Last updated February 4, 2025
Ask Ithy AI
Download Article
Delete Article