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:
- Unified Monitoring: Combine metrics, logs, and traces in one platform
- Powerful Analytics: Kusto Query Language for advanced log analysis
- Smart Alerts: AI-driven anomaly detection and alerting
- Integration: Seamless integration with Azure ecosystem
- 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.