Azure Monitor in Java: Complete Integration Guide


Article

Azure Monitor provides comprehensive monitoring solutions for applications running on Azure, on-premises, or other cloud platforms. This guide covers how to integrate Azure Monitor into Java applications using the Azure SDK, custom metrics, logs, distributed tracing, and best practices.

Azure Monitor Components for Java

  • Application Insights: APM solution for performance monitoring
  • Azure Monitor Metrics: Time-series data for numerical values
  • Azure Monitor Logs: Log analytics with Kusto Query Language (KQL)
  • Distributed Tracing: End-to-end transaction tracking
  • Custom Telemetry: Business-specific monitoring data

Project Setup and Dependencies

Maven Dependencies:

<properties>
<azure.version>1.41.0</azure.version>
<applicationinsights.version>3.4.18</applicationinsights.version>
<opentelemetry.version>1.32.0</opentelemetry.version>
</properties>
<dependencies>
<!-- Azure Monitor Core -->
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-core</artifactId>
<version>${azure.version}</version>
</dependency>
<!-- Azure Monitor OpenTelemetry Distro -->
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-monitor-opentelemetry-autoconfigure</artifactId>
<version>1.0.0-beta.17</version>
</dependency>
<!-- Application Insights -->
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>applicationinsights-core</artifactId>
<version>${applicationinsights.version}</version>
</dependency>
<!-- Application Insights Web (for HTTP monitoring) -->
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>applicationinsights-web</artifactId>
<version>${applicationinsights.version}</version>
</dependency>
<!-- Azure Identity -->
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.10.0</version>
</dependency>
<!-- OpenTelemetry API -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>${opentelemetry.version}</version>
</dependency>
<!-- Micrometer Azure Monitor -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-azure-monitor</artifactId>
<version>1.11.5</version>
</dependency>
<!-- Spring Boot Starter (if using Spring) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>

1. Application Insights Integration

ApplicationInsights Configuration:

package com.example.azuremonitor.config;
import com.microsoft.applicationinsights.TelemetryConfiguration;
import com.microsoft.applicationinsights.web.internal.WebRequestTrackingFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
public class ApplicationInsightsConfig {
@PostConstruct
public void initializeApplicationInsights() {
String instrumentationKey = System.getenv("APPINSIGHTS_INSTRUMENTATIONKEY");
if (instrumentationKey != null && !instrumentationKey.isEmpty()) {
TelemetryConfiguration.getActive().setInstrumentationKey(instrumentationKey);
}
}
@Bean
public FilterRegistrationBean<WebRequestTrackingFilter> webRequestTrackingFilter() {
FilterRegistrationBean<WebRequestTrackingFilter> registrationBean = 
new FilterRegistrationBean<>();
registrationBean.setFilter(new WebRequestTrackingFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1);
return registrationBean;
}
}

Custom Telemetry Service:

package com.example.azuremonitor.service;
import com.microsoft.applicationinsights.TelemetryClient;
import com.microsoft.applicationinsights.telemetry.*;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@Service
public class ApplicationInsightsService {
private final TelemetryClient telemetryClient;
public ApplicationInsightsService() {
this.telemetryClient = new TelemetryClient();
}
// Track custom events
public void trackEvent(String eventName, Map<String, String> properties, 
Map<String, Double> metrics) {
EventTelemetry event = new EventTelemetry(eventName);
if (properties != null) {
properties.forEach(event.getProperties()::put);
}
if (metrics != null) {
metrics.forEach(event.getMetrics()::put);
}
telemetryClient.trackEvent(event);
}
// Track custom metrics
public void trackMetric(String metricName, double value, 
Map<String, String> properties) {
MetricTelemetry metric = new MetricTelemetry(metricName, value);
if (properties != null) {
properties.forEach(metric.getProperties()::put);
}
telemetryClient.trackMetric(metric);
}
// Track dependencies (external service calls)
public void trackDependency(String dependencyName, String commandName, 
long duration, boolean success) {
DependencyTelemetry dependency = new DependencyTelemetry();
dependency.setName(dependencyName);
dependency.setData(commandName);
dependency.setDuration(duration);
dependency.setSuccess(success);
dependency.setTimestamp(new Date());
telemetryClient.trackDependency(dependency);
}
// Track exceptions
public void trackException(Exception exception, Map<String, String> properties, 
Map<String, Double> metrics) {
ExceptionTelemetry exceptionTelemetry = new ExceptionTelemetry(exception);
if (properties != null) {
properties.forEach(exceptionTelemetry.getProperties()::put);
}
if (metrics != null) {
metrics.forEach(exceptionTelemetry.getMetrics()::put);
}
telemetryClient.trackException(exceptionTelemetry);
}
// Track business transactions
public void trackBusinessTransaction(String transactionName, String userId, 
double amount, String status) {
Map<String, String> properties = new HashMap<>();
properties.put("TransactionName", transactionName);
properties.put("UserId", userId);
properties.put("Status", status);
Map<String, Double> metrics = new HashMap<>();
metrics.put("Amount", amount);
trackEvent("BusinessTransaction", properties, metrics);
}
// Track page views
public void trackPageView(String pageName, long duration) {
PageViewTelemetry pageView = new PageViewTelemetry();
pageView.setName(pageName);
pageView.setDuration(duration);
telemetryClient.trackPageView(pageView);
}
// Track requests
public void trackRequest(String requestName, long duration, boolean success) {
RequestTelemetry request = new RequestTelemetry();
request.setName(requestName);
request.setDuration(duration);
request.setSuccess(success);
request.setTimestamp(new Date());
telemetryClient.trackRequest(request);
}
// Flush telemetry asynchronously
public CompletableFuture<Void> flushAsync() {
return CompletableFuture.runAsync(() -> {
telemetryClient.flush();
try {
// Wait for flush to complete
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// Track availability test
public void trackAvailability(String testName, String runLocation, 
long duration, boolean success, String message) {
AvailabilityTelemetry availability = new AvailabilityTelemetry();
availability.setName(testName);
availability.setRunLocation(runLocation);
availability.setDuration(duration);
availability.setSuccess(success);
availability.setMessage(message);
telemetryClient.trackAvailability(availability);
}
}

2. Azure Monitor Metrics with Micrometer

Micrometer Azure Monitor Configuration:

package com.example.azuremonitor.config;
import com.azure.monitor.opentelemetry.exporter.AzureMonitorExporterBuilder;
import com.azure.monitor.opentelemetry.exporter.AzureMonitorTraceExporter;
import io.micrometer.azuremonitor.AzureMonitorConfig;
import io.micrometer.azuremonitor.AzureMonitorMeterRegistry;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.MeterRegistry;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.ResourceAttributes;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
@Configuration
public class AzureMonitorMetricsConfig {
@Bean
public AzureMonitorConfig azureMonitorConfig() {
return new AzureMonitorConfig() {
@Override
public String get(String key) {
return null;
}
@Override
public String instrumentationKey() {
return System.getenv("APPINSIGHTS_INSTRUMENTATIONKEY");
}
@Override
public Duration step() {
return Duration.ofSeconds(30);
}
};
}
@Bean
public MeterRegistry azureMonitorMeterRegistry(AzureMonitorConfig config) {
AzureMonitorMeterRegistry registry = new AzureMonitorMeterRegistry(config, Clock.SYSTEM);
// Add common tags
registry.config().commonTags(
"application", "order-service",
"environment", System.getenv("ENVIRONMENT") != null ? 
System.getenv("ENVIRONMENT") : "development",
"region", System.getenv("REGION") != null ? 
System.getenv("REGION") : "eastus"
);
return registry;
}
@Bean
public Tracer openTelemetryTracer() {
String connectionString = System.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING");
if (connectionString == null || connectionString.isEmpty()) {
// Return a no-op tracer if no connection string
return io.opentelemetry.api.trace.Tracer.noop();
}
AzureMonitorTraceExporter exporter = new AzureMonitorExporterBuilder()
.connectionString(connectionString)
.buildTraceExporter();
Resource resource = Resource.getDefault()
.merge(Resource.create(Attributes.builder()
.put(ResourceAttributes.SERVICE_NAME, "order-service")
.put(ResourceAttributes.SERVICE_VERSION, "1.0.0")
.put(ResourceAttributes.DEPLOYMENT_ENVIRONMENT, 
System.getenv("ENVIRONMENT") != null ? 
System.getenv("ENVIRONMENT") : "development")
.build()));
OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder()
.setResource(resource)
.build();
GlobalOpenTelemetry.set(openTelemetrySdk);
return openTelemetrySdk.getTracer("com.example.orders");
}
}

Custom Metrics Service:

package com.example.azuremonitor.service;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class AzureMetricsService {
private final MeterRegistry meterRegistry;
private final Counter httpRequests;
private final Counter businessEvents;
private final Timer httpRequestDuration;
private final AtomicInteger activeUsers;
private final AtomicLong cacheHits;
private final AtomicLong cacheMisses;
private final ConcurrentHashMap<String, Counter> customCounters;
public AzureMetricsService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.customCounters = new ConcurrentHashMap<>();
// Initialize core metrics
this.httpRequests = Counter.builder("http.requests.total")
.description("Total HTTP requests")
.tag("application", "order-service")
.register(meterRegistry);
this.businessEvents = Counter.builder("business.events.total")
.description("Total business events")
.register(meterRegistry);
this.httpRequestDuration = Timer.builder("http.request.duration")
.description("HTTP request duration")
.publishPercentiles(0.5, 0.95, 0.99)
.register(meterRegistry);
this.activeUsers = new AtomicInteger(0);
Gauge.builder("application.active.users", activeUsers, AtomicInteger::get)
.description("Number of active users")
.register(meterRegistry);
this.cacheHits = new AtomicLong(0);
this.cacheMisses = new AtomicLong(0);
// Cache effectiveness gauge
Gauge.builder("cache.hit.ratio", this, AzureMetricsService::calculateCacheHitRatio)
.description("Cache hit ratio")
.register(meterRegistry);
}
// HTTP metrics
public void recordHttpRequest(String method, String path, int status, long durationMs) {
httpRequests.increment();
httpRequestDuration.record(durationMs, TimeUnit.MILLISECONDS);
// Record detailed HTTP metrics
Counter.builder("http.requests.detailed")
.tag("method", method)
.tag("path", path)
.tag("status", String.valueOf(status))
.register(meterRegistry)
.increment();
}
// Business metrics
public void recordBusinessEvent(String eventType, String entityType) {
businessEvents.increment();
Counter.builder("business.events.detailed")
.tag("event.type", eventType)
.tag("entity.type", entityType)
.register(meterRegistry)
.increment();
}
public void recordOrderCreated(String customerId, double amount, String currency) {
businessEvents.increment();
Counter.builder("orders.created.total")
.tag("currency", currency)
.register(meterRegistry)
.increment();
// Record order value
meterRegistry.summary("orders.amount")
.record(amount);
}
// User activity metrics
public void userLoggedIn() {
activeUsers.incrementAndGet();
}
public void userLoggedOut() {
activeUsers.decrementAndGet();
}
// Cache metrics
public void recordCacheHit() {
cacheHits.incrementAndGet();
}
public void recordCacheMiss() {
cacheMisses.incrementAndGet();
}
private double calculateCacheHitRatio() {
long hits = cacheHits.get();
long misses = cacheMisses.get();
long total = hits + misses;
return total > 0 ? (double) hits / total * 100 : 0;
}
// Database metrics
public void recordDatabaseQuery(String queryType, long durationMs, boolean success) {
Timer.builder("database.query.duration")
.tag("query.type", queryType)
.tag("success", String.valueOf(success))
.register(meterRegistry)
.record(durationMs, TimeUnit.MILLISECONDS);
Counter.builder("database.queries.total")
.tag("query.type", queryType)
.tag("success", String.valueOf(success))
.register(meterRegistry)
.increment();
}
// Custom counter with dynamic tags
public void incrementCounter(String name, String... tags) {
String counterKey = name + String.join("", tags);
Counter counter = customCounters.computeIfAbsent(counterKey, k -> 
Counter.builder(name)
.tags(tags)
.register(meterRegistry)
);
counter.increment();
}
// Gauge for business metrics
public void registerBusinessGauge(String name, AtomicLong value, String... tags) {
Gauge.builder(name, value, AtomicLong::get)
.tags(tags)
.register(meterRegistry);
}
// Performance metrics
public Timer.Sample startTimer() {
return Timer.start(meterRegistry);
}
public void stopTimer(Timer.Sample sample, String timerName, String... tags) {
sample.stop(Timer.builder(timerName)
.tags(tags)
.register(meterRegistry));
}
}

3. OpenTelemetry with Azure Monitor

Distributed Tracing Configuration:

package com.example.azuremonitor.tracing;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
@Component
public class AzureDistributedTracing {
private final Tracer tracer;
public AzureDistributedTracing() {
this.tracer = GlobalOpenTelemetry.getTracer("order-service");
}
// Create span for method execution
public <T> T trace(String spanName, Map<String, Object> attributes, Supplier<T> operation) {
Span span = tracer.spanBuilder(spanName).startSpan();
// Set attributes
if (attributes != null) {
attributes.forEach((key, value) -> 
span.setAttribute(key, value.toString()));
}
try (Scope scope = span.makeCurrent()) {
T result = operation.get();
span.setStatus(io.opentelemetry.api.trace.StatusCode.OK);
return result;
} catch (Exception e) {
span.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR);
span.recordException(e);
throw e;
} finally {
span.end();
}
}
// Trace async operations
public <T> CompletableFuture<T> traceAsync(String spanName, Map<String, Object> attributes, 
Supplier<CompletableFuture<T>> operation) {
Span span = tracer.spanBuilder(spanName).startSpan();
if (attributes != null) {
attributes.forEach((key, value) -> 
span.setAttribute(key, value.toString()));
}
try (Scope scope = span.makeCurrent()) {
return operation.get()
.whenComplete((result, throwable) -> {
if (throwable != null) {
span.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR);
span.recordException(throwable);
} else {
span.setStatus(io.opentelemetry.api.trace.StatusCode.OK);
}
span.end();
});
}
}
// Add custom attributes to current span
public void addSpanAttributes(Map<String, Object> attributes) {
Span currentSpan = Span.current();
if (currentSpan != null && attributes != null) {
attributes.forEach((key, value) -> 
currentSpan.setAttribute(key, value.toString()));
}
}
// Record business events in traces
public void recordBusinessEvent(String eventName, Map<String, Object> properties) {
Span currentSpan = Span.current();
if (currentSpan != null) {
currentSpan.addEvent(eventName);
if (properties != null) {
properties.forEach((key, value) -> 
currentSpan.setAttribute("event." + key, value.toString()));
}
}
}
// Create span for HTTP requests
public Span startHttpSpan(String method, String path, Map<String, String> headers) {
return tracer.spanBuilder(String.format("%s %s", method, path))
.setAttribute("http.method", method)
.setAttribute("http.path", path)
.setAttribute("http.url", path)
.startSpan();
}
}

4. Spring Boot Integration

Azure Monitor Auto-Configuration:

package com.example.azuremonitor.config;
import com.azure.core.credential.TokenCredential;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.monitor.ingestion.LogsIngestionClient;
import com.azure.monitor.ingestion.LogsIngestionClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.metrics.export.azuremonitor.AzureMonitorProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(AzureMonitorProperties.class)
public class AzureMonitorAutoConfig {
@Value("${azure.monitor.data-collection-endpoint:}")
private String dataCollectionEndpoint;
@Value("${azure.monitor.data-collection-rule-id:}")
private String dataCollectionRuleId;
@Bean
public TokenCredential azureCredential() {
return new DefaultAzureCredentialBuilder().build();
}
@Bean
public LogsIngestionClient logsIngestionClient(TokenCredential credential) {
if (dataCollectionEndpoint == null || dataCollectionEndpoint.isEmpty()) {
return null;
}
return new LogsIngestionClientBuilder()
.endpoint(dataCollectionEndpoint)
.credential(credential)
.buildClient();
}
@Bean
public AzureLogsService azureLogsService(LogsIngestionClient logsClient) {
return new AzureLogsService(logsClient, dataCollectionRuleId);
}
}

Custom Logs Service for Azure Monitor Logs:

package com.example.azuremonitor.service;
import com.azure.monitor.ingestion.LogsIngestionClient;
import com.azure.monitor.ingestion.models.UploadLogsResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class AzureLogsService {
private final LogsIngestionClient logsClient;
private final String dataCollectionRuleId;
private final ObjectMapper objectMapper;
private final String customTableName = "CustomAppLogs_CL";
public AzureLogsService(LogsIngestionClient logsClient, String dataCollectionRuleId) {
this.logsClient = logsClient;
this.dataCollectionRuleId = dataCollectionRuleId;
this.objectMapper = new ObjectMapper();
}
public void sendCustomLog(Map<String, Object> logData) {
if (logsClient == null || dataCollectionRuleId == null) {
return; // Skip if not configured
}
try {
// Add common fields
logData.putIfAbsent("TimeGenerated", OffsetDateTime.now());
logData.putIfAbsent("Application", "order-service");
logData.putIfAbsent("Environment", 
System.getenv("ENVIRONMENT") != null ? 
System.getenv("ENVIRONMENT") : "development");
List<Object> logs = new ArrayList<>();
logs.add(logData);
UploadLogsResult result = logsClient.upload(
dataCollectionRuleId,
customTableName,
logs
);
// Log upload result if needed
if (result.getStatus() != UploadLogsResult.Status.SUCCESS) {
System.err.println("Failed to upload logs: " + result.getStatus());
}
} catch (Exception e) {
System.err.println("Error sending logs to Azure Monitor: " + e.getMessage());
}
}
public void sendBusinessLog(String operation, String entityId, String status, 
Map<String, Object> additionalData) {
Map<String, Object> logEntry = new HashMap<>();
logEntry.put("Operation", operation);
logEntry.put("EntityId", entityId);
logEntry.put("Status", status);
logEntry.put("LogType", "BusinessOperation");
if (additionalData != null) {
logEntry.putAll(additionalData);
}
sendCustomLog(logEntry);
}
public void sendPerformanceLog(String operation, long durationMs, 
boolean success, Map<String, Object> context) {
Map<String, Object> logEntry = new HashMap<>();
logEntry.put("Operation", operation);
logEntry.put("DurationMs", durationMs);
logEntry.put("Success", success);
logEntry.put("LogType", "Performance");
if (context != null) {
logEntry.putAll(context);
}
sendCustomLog(logEntry);
}
public void sendSecurityLog(String eventType, String userId, String resource, 
String action, Map<String, Object> details) {
Map<String, Object> logEntry = new HashMap<>();
logEntry.put("EventType", eventType);
logEntry.put("UserId", userId);
logEntry.put("Resource", resource);
logEntry.put("Action", action);
logEntry.put("LogType", "Security");
if (details != null) {
logEntry.putAll(details);
}
sendCustomLog(logEntry);
}
}

5. Spring Boot Web Integration

Request/Response Monitoring:

package com.example.azuremonitor.web;
import com.example.azuremonitor.service.AzureMetricsService;
import com.example.azuremonitor.service.ApplicationInsightsService;
import com.example.azuremonitor.tracing.AzureDistributedTracing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Component
public class AzureMonitorInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(AzureMonitorInterceptor.class);
private final AzureMetricsService metricsService;
private final ApplicationInsightsService appInsightsService;
private final AzureDistributedTracing tracing;
public AzureMonitorInterceptor(AzureMetricsService metricsService,
ApplicationInsightsService appInsightsService,
AzureDistributedTracing tracing) {
this.metricsService = metricsService;
this.appInsightsService = appInsightsService;
this.tracing = tracing;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
Object handler) {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
// Start tracing span
Map<String, Object> spanAttributes = new HashMap<>();
spanAttributes.put("http.method", request.getMethod());
spanAttributes.put("http.path", request.getRequestURI());
spanAttributes.put("http.user_agent", request.getHeader("User-Agent"));
spanAttributes.put("http.client_ip", getClientIp(request));
tracing.addSpanAttributes(spanAttributes);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
Long startTime = (Long) request.getAttribute("startTime");
long duration = System.currentTimeMillis() - startTime;
int status = response.getStatus();
// Record metrics
metricsService.recordHttpRequest(
request.getMethod(),
request.getRequestURI(),
status,
duration
);
// Record in Application Insights
appInsightsService.trackRequest(
request.getMethod() + " " + request.getRequestURI(),
duration,
status < 400
);
// Record errors
if (ex != null) {
Map<String, String> properties = new HashMap<>();
properties.put("Method", request.getMethod());
properties.put("Path", request.getRequestURI());
appInsightsService.trackException(ex, properties, null);
}
// Record business event for significant operations
if (isBusinessOperation(request)) {
tracing.recordBusinessEvent("HTTP Request Completed", Map.of(
"method", request.getMethod(),
"path", request.getRequestURI(),
"status", status,
"duration", duration
));
}
}
private String getClientIp(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
return request.getRemoteAddr();
}
private boolean isBusinessOperation(HttpServletRequest request) {
String path = request.getRequestURI();
return path.contains("/api/") && 
(path.contains("/orders") || path.contains("/customers"));
}
}

6. Business Service Integration

Order Service with Azure Monitor:

package com.example.azuremonitor.service;
import com.example.azuremonitor.tracing.AzureDistributedTracing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@Service
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
private final AzureMetricsService metricsService;
private final ApplicationInsightsService appInsightsService;
private final AzureDistributedTracing tracing;
private final AzureLogsService logsService;
public OrderService(AzureMetricsService metricsService,
ApplicationInsightsService appInsightsService,
AzureDistributedTracing tracing,
AzureLogsService logsService) {
this.metricsService = metricsService;
this.appInsightsService = appInsightsService;
this.tracing = tracing;
this.logsService = logsService;
}
public Order createOrder(CreateOrderRequest request) {
return tracing.trace("OrderService.createOrder", 
Map.of(
"order.customerId", request.getCustomerId(),
"order.items.count", request.getItems().size(),
"order.total.amount", request.getTotalAmount()
),
() -> {
try {
// Record business event
metricsService.recordBusinessEvent("order_creation", "order");
// Process order logic
Order order = processOrder(request);
// Track success in Application Insights
appInsightsService.trackBusinessTransaction(
"OrderCreated",
request.getCustomerId(),
request.getTotalAmount(),
"SUCCESS"
);
// Send custom log
logsService.sendBusinessLog(
"CreateOrder",
order.getId(),
"SUCCESS",
Map.of(
"CustomerId", request.getCustomerId(),
"OrderAmount", request.getTotalAmount(),
"ItemsCount", request.getItems().size()
)
);
// Record metrics
metricsService.recordOrderCreated(
request.getCustomerId(),
request.getTotalAmount(),
request.getCurrency()
);
return order;
} catch (Exception e) {
// Track failure
appInsightsService.trackBusinessTransaction(
"OrderCreated",
request.getCustomerId(),
request.getTotalAmount(),
"FAILED"
);
Map<String, String> properties = Map.of(
"CustomerId", request.getCustomerId(),
"Operation", "CreateOrder"
);
appInsightsService.trackException(e, properties, null);
logsService.sendBusinessLog(
"CreateOrder",
null,
"FAILED",
Map.of(
"CustomerId", request.getCustomerId(),
"Error", e.getMessage()
)
);
throw e;
}
});
}
public CompletableFuture<Order> processOrderAsync(Order order) {
return tracing.traceAsync("OrderService.processOrderAsync",
Map.of("order.id", order.getId()),
() -> CompletableFuture.supplyAsync(() -> {
long startTime = System.currentTimeMillis();
try {
// Simulate processing
Thread.sleep(100);
metricsService.recordBusinessEvent("order_processed", "order");
long duration = System.currentTimeMillis() - startTime;
logsService.sendPerformanceLog(
"ProcessOrder",
duration,
true,
Map.of("OrderId", order.getId())
);
return order;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
logsService.sendPerformanceLog(
"ProcessOrder",
duration,
false,
Map.of(
"OrderId", order.getId(),
"Error", e.getMessage()
)
);
throw new RuntimeException(e);
}
}));
}
private Order processOrder(CreateOrderRequest request) {
// Order processing logic
return new Order(
java.util.UUID.randomUUID().toString(),
request.getCustomerId(),
request.getItems(),
request.getTotalAmount(),
"CREATED"
);
}
}

7. Configuration Files

application.yml:

azure:
monitor:
data-collection-endpoint: ${AZURE_MONITOR_DCE:}
data-collection-rule-id: ${AZURE_MONITOR_DCR_ID:}
management:
endpoints:
web:
exposure:
include: health,metrics,info
endpoint:
health:
show-details: always
metrics:
export:
azuremonitor:
enabled: true
instrumentation-key: ${APPINSIGHTS_INSTRUMENTATIONKEY:}
step: 30s
logging:
level:
com.example.azuremonitor: DEBUG
app:
name: order-service
version: 1.0.0

8. Docker Deployment

Dockerfile:

FROM openjdk:17-jre-slim
# Install necessary tools
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
# Copy application
COPY target/order-service.jar /app/
# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
WORKDIR /app
# Set Azure Monitor environment variables
ENV APPLICATIONINSIGHTS_CONNECTION_STRING=""
ENV APPINSIGHTS_INSTRUMENTATIONKEY=""
ENV AZURE_MONITOR_DCE=""
ENV AZURE_MONITOR_DCR_ID=""
ENV ENVIRONMENT="production"
EXPOSE 8080
CMD ["java", "-jar", "order-service.jar"]

docker-compose.yml:

version: '3.8'
services:
order-service:
build: .
ports:
- "8080:8080"
environment:
- APPLICATIONINSIGHTS_CONNECTION_STRING=${APPLICATIONINSIGHTS_CONNECTION_STRING}
- APPINSIGHTS_INSTRUMENTATIONKEY=${APPINSIGHTS_INSTRUMENTATIONKEY}
- AZURE_MONITOR_DCE=${AZURE_MONITOR_DCE}
- AZURE_MONITOR_DCR_ID=${AZURE_MONITOR_DCR_ID}
- ENVIRONMENT=production
- JAVA_OPTS=-Xms512m -Xmx1024m
restart: unless-stopped

9. Kubernetes Deployment

ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
name: order-service-config
data:
application.yml: |
management:
endpoints:
web:
exposure:
include: health,metrics,info
metrics:
export:
azuremonitor:
enabled: true
step: 30s
logging:
level:
com.example.azuremonitor: INFO

Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/actuator/prometheus"
spec:
containers:
- name: order-service
image: your-registry/order-service:latest
ports:
- containerPort: 8080
env:
- name: APPLICATIONINSIGHTS_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: azure-secrets
key: applicationinsights-connection-string
- name: APPINSIGHTS_INSTRUMENTATIONKEY
valueFrom:
secretKeyRef:
name: azure-secrets
key: appinsights-instrumentationkey
- name: ENVIRONMENT
value: "production"
- name: JAVA_OPTS
value: "-Xms512m -Xmx1024m -javaagent:/app/applicationinsights-agent.jar"
volumeMounts:
- name: config
mountPath: /app/config
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 20
periodSeconds: 5
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
volumes:
- name: config
configMap:
name: order-service-config

10. Best Practices

Performance Optimization:

  • Use async operations for telemetry submission
  • Implement batching for high-volume metrics
  • Configure appropriate sampling rates
  • Use connection pooling for HTTP clients

Error Handling:

@Component
public class AzureMonitorErrorHandler {
private final Counter telemetryErrors;
public AzureMonitorErrorHandler(MeterRegistry registry) {
this.telemetryErrors = Counter.builder("azure.monitor.errors")
.register(registry);
}
public void handleError(String operation, Exception e) {
telemetryErrors.increment();
logger.error("Azure Monitor operation failed: {}", operation, e);
// Implement fallback logging or alerting
if (e instanceof com.azure.core.exception.HttpResponseException) {
// Handle specific Azure errors
handleAzureError((com.azure.core.exception.HttpResponseException) e);
}
}
private void handleAzureError(com.azure.core.exception.HttpResponseException e) {
// Implement specific error handling for Azure services
logger.warn("Azure service error: {} - {}", e.getStatusCode(), e.getMessage());
}
}

Security:

  • Use Managed Identity when running on Azure
  • Secure connection strings and keys
  • Implement proper RBAC for Azure resources
  • Encrypt sensitive data in logs

Conclusion

Azure Monitor provides a comprehensive observability solution for Java applications. Key benefits include:

  1. Unified Monitoring: Combine metrics, logs, and traces in one platform
  2. Powerful Analytics: Kusto Query Language for advanced log analysis
  3. Smart Alerts: AI-driven anomaly detection and alerting
  4. Integration: Seamless integration with Azure ecosystem
  5. Cost-Effective: Flexible pricing based on data volume

By implementing the patterns shown above, you can achieve full-stack observability for your Java applications, from infrastructure metrics to business-level KPIs, all within the Azure Monitor ecosystem.

Leave a Reply

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


Macro Nepal Helper