Synthetic Monitoring in Java: Comprehensive Guide to Proactive Application Monitoring

Synthetic monitoring involves simulating user interactions and transactions to proactively monitor application health, performance, and functionality from external perspectives.

Project Setup and Dependencies

1. Maven Dependencies
<dependencies>
<!-- Web Client -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>3.1.0</version>
</dependency>
<!-- Selenium for Browser Automation -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.15.0</version>
</dependency>
<!-- Selenium Grid -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-grid</artifactId>
<version>4.15.0</version>
</dependency>
<!-- Metrics and Monitoring -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.11.5</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.11.5</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- Scheduling -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>3.1.0</version>
</dependency>
<!-- Database -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Email Notifications -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>3.1.0</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.35.0</version>
<scope>test</scope>
</dependency>
</dependencies>

Core Monitoring Models and Configuration

1. Monitoring Configuration
// MonitorConfig.java
@Data
@Configuration
@ConfigurationProperties(prefix = "synthetic.monitoring")
public class MonitorConfig {
private boolean enabled = true;
private int defaultTimeoutSeconds = 30;
private int maxConcurrentMonitors = 10;
private String defaultUserAgent = "SyntheticMonitor/1.0";
private List<String> defaultRegions = List.of("us-east-1", "eu-west-1", "ap-southeast-1");
private AlertConfig alert = new AlertConfig();
private RetentionConfig retention = new RetentionConfig();
private BrowserConfig browser = new BrowserConfig();
@Data
public static class AlertConfig {
private boolean enabled = true;
private int failureThreshold = 3;
private int recoveryThreshold = 2;
private List<String> notificationChannels = List.of("email", "slack");
private String pagerDutyServiceKey;
}
@Data
public static class RetentionConfig {
private int resultsDays = 30;
private int metricsDays = 90;
private int tracesDays = 7;
}
@Data
public static class BrowserConfig {
private boolean headless = true;
private int pageLoadTimeoutSeconds = 30;
private int scriptTimeoutSeconds = 10;
private String chromeDriverPath;
private List<String> browserArgs = List.of("--no-sandbox", "--disable-dev-shm-usage");
}
}
// MonitorType.java
public enum MonitorType {
HTTP_ENDPOINT("HTTP Endpoint Check"),
BROWSER_TRANSACTION("Browser Transaction"),
API_SEQUENCE("API Sequence"),
REAL_BROWSER("Real Browser Check"),
MULTI_STEP("Multi-step Transaction"),
WEBSOCKET("WebSocket Check"),
DNS("DNS Resolution"),
SSL_CERTIFICATE("SSL Certificate Check");
private final String description;
MonitorType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
// MonitorFrequency.java
public enum MonitorFrequency {
ONE_MINUTE(60),
FIVE_MINUTES(300),
FIFTEEN_MINUTES(900),
THIRTY_MINUTES(1800),
ONE_HOUR(3600);
private final int seconds;
MonitorFrequency(int seconds) {
this.seconds = seconds;
}
public int getSeconds() {
return seconds;
}
public Duration getDuration() {
return Duration.ofSeconds(seconds);
}
}
2. Monitor Definition Models
// SyntheticMonitor.java
@Entity
@Table(name = "synthetic_monitors")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SyntheticMonitor {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
private String name;
@NotBlank
private String description;
@Enumerated(EnumType.STRING)
private MonitorType type;
@Enumerated(EnumType.STRING)
private MonitorFrequency frequency;
private boolean enabled = true;
@ElementCollection
private List<String> regions;
@Column(columnDefinition = "TEXT")
private String configuration;
@CreationTimestamp
private ZonedDateTime createdAt;
@UpdateTimestamp
private ZonedDateTime updatedAt;
private String createdBy;
private String updatedBy;
@OneToMany(mappedBy = "monitor", cascade = CascadeType.ALL)
private List<MonitorExecution> executions;
@ElementCollection
private Map<String, String> tags = new HashMap<>();
}
// MonitorConfiguration.java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MonitorConfiguration {
// HTTP Configuration
private String url;
private HttpMethod method;
private Map<String, String> headers;
private String body;
private Integer expectedStatus;
private String expectedBodyPattern;
private Integer timeoutSeconds;
// Browser Configuration
private List<BrowserStep> browserSteps;
private String browserScript;
private Boolean takeScreenshot;
private Boolean captureHar;
// Validation Rules
private List<ValidationRule> validationRules;
private List<Assertion> assertions;
// Multi-step configuration
private List<MonitorStep> steps;
// SSL Configuration
private Integer sslExpiryWarningDays;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class BrowserStep {
private String action; // click, type, wait, navigate, etc.
private String selector;
private String value;
private Integer timeoutSeconds;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ValidationRule {
private String type; // status_code, response_time, body_content, header
private String field;
private String operator; // equals, contains, matches, gt, lt
private String expectedValue;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Assertion {
private String name;
private String condition;
private String expected;
private Severity severity;
public enum Severity {
CRITICAL, HIGH, MEDIUM, LOW
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class MonitorStep {
private String name;
private String url;
private HttpMethod method;
private Map<String, String> headers;
private String body;
private List<ValidationRule> validations;
private Map<String, String> extractors; // Extract values for subsequent steps
}
}
3. Execution Results and Metrics
// MonitorExecution.java
@Entity
@Table(name = "monitor_executions")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MonitorExecution {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "monitor_id")
private SyntheticMonitor monitor;
private String region;
private String workerId;
@Enumerated(EnumType.STRING)
private ExecutionStatus status;
private ZonedDateTime startedAt;
private ZonedDateTime completedAt;
private Long responseTimeMs;
private Integer statusCode;
@Column(columnDefinition = "TEXT")
private String responseBody;
@Column(columnDefinition = "TEXT")
private String errorMessage;
@Column(columnDefinition = "TEXT")
private String stackTrace;
@ElementCollection
private Map<String, String> metrics = new HashMap<>();
@Column(columnDefinition = "TEXT")
private String validationResults;
private String screenshotPath;
private String harFilePath;
@ElementCollection
private Map<String, String> extractedValues = new HashMap<>();
public Duration getDuration() {
if (startedAt != null && completedAt != null) {
return Duration.between(startedAt, completedAt);
}
return Duration.ZERO;
}
public boolean isSuccessful() {
return status == ExecutionStatus.SUCCESS;
}
}
// ExecutionStatus.java
public enum ExecutionStatus {
SUCCESS,
FAILED,
TIMEOUT,
VALIDATION_FAILED,
NETWORK_ERROR,
SSL_ERROR,
BROWSER_ERROR
}
// MonitorMetrics.java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MonitorMetrics {
private String monitorId;
private String region;
private ZonedDateTime timestamp;
// Response Time Metrics
private Double responseTimeP50;
private Double responseTimeP95;
private Double responseTimeP99;
private Double responseTimeMax;
// Availability Metrics
private Double availability;
private Integer totalExecutions;
private Integer successfulExecutions;
private Integer failedExecutions;
// Business Metrics
private Map<String, Double> customMetrics = new HashMap<>();
// Error Breakdown
private Map<ExecutionStatus, Integer> errorBreakdown = new HashMap<>();
}

Core Monitoring Engine

1. Base Monitor Interface and Factory
// MonitorExecutor.java
public interface MonitorExecutor {
MonitorExecution execute(SyntheticMonitor monitor);
boolean supports(MonitorType monitorType);
}
// MonitorExecutorFactory.java
@Component
@Slf4j
public class MonitorExecutorFactory {
private final Map<MonitorType, MonitorExecutor> executors;
@Autowired
public MonitorExecutorFactory(List<MonitorExecutor> executorList) {
this.executors = executorList.stream()
.collect(Collectors.toMap(
executor -> executor.getClass().getAnnotation(MonitorExecutorType.class).value(),
Function.identity()
));
}
public MonitorExecutor getExecutor(MonitorType monitorType) {
MonitorExecutor executor = executors.get(monitorType);
if (executor == null) {
throw new IllegalArgumentException("No executor found for monitor type: " + monitorType);
}
return executor;
}
}
// MonitorExecutorType.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MonitorExecutorType {
MonitorType value();
}
2. HTTP Endpoint Monitor
// HttpEndpointExecutor.java
@Component
@MonitorExecutorType(MonitorType.HTTP_ENDPOINT)
@Slf4j
public class HttpEndpointExecutor implements MonitorExecutor {
private final WebClient webClient;
private final ObjectMapper objectMapper;
public HttpEndpointExecutor(WebClient.Builder webClientBuilder, 
ObjectMapper objectMapper) {
this.webClient = webClientBuilder.build();
this.objectMapper = objectMapper;
}
@Override
public MonitorExecution execute(SyntheticMonitor monitor) {
MonitorExecution execution = MonitorExecution.builder()
.monitor(monitor)
.region("default")
.startedAt(ZonedDateTime.now())
.status(ExecutionStatus.FAILED)
.build();
try {
MonitorConfiguration config = parseConfiguration(monitor);
validateConfiguration(config);
WebClient.RequestBodySpec request = buildRequest(config);
long startTime = System.currentTimeMillis();
String responseBody = request
.retrieve()
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(config.getTimeoutSeconds() != null ? 
config.getTimeoutSeconds() : 30))
.block();
long responseTime = System.currentTimeMillis() - startTime;
execution.setResponseTimeMs(responseTime);
execution.setResponseBody(truncateResponseBody(responseBody));
execution.setStatusCode(200); // Assuming success for now
// Validate response
ValidationResult validationResult = validateResponse(config, responseBody, responseTime);
execution.setValidationResults(toJson(validationResult));
if (validationResult.isSuccess()) {
execution.setStatus(ExecutionStatus.SUCCESS);
execution.getMetrics().put("response_time_ms", String.valueOf(responseTime));
execution.getMetrics().put("content_size_bytes", 
String.valueOf(responseBody != null ? responseBody.length() : 0));
} else {
execution.setStatus(ExecutionStatus.VALIDATION_FAILED);
execution.setErrorMessage("Validation failed: " + validationResult.getFailures());
}
} catch (Exception e) {
log.error("HTTP monitor execution failed for {}: {}", monitor.getName(), e.getMessage(), e);
execution.setErrorMessage(e.getMessage());
execution.setStackTrace(getStackTrace(e));
execution.setStatus(determineErrorStatus(e));
} finally {
execution.setCompletedAt(ZonedDateTime.now());
}
return execution;
}
@Override
public boolean supports(MonitorType monitorType) {
return monitorType == MonitorType.HTTP_ENDPOINT;
}
private WebClient.RequestBodySpec buildRequest(MonitorConfiguration config) {
WebClient.RequestBodySpec request = webClient
.method(config.getMethod())
.uri(config.getUrl());
// Add headers
if (config.getHeaders() != null) {
config.getHeaders().forEach(request::header);
}
// Add body for POST, PUT, PATCH
if (config.getBody() != null && 
(config.getMethod() == HttpMethod.POST || 
config.getMethod() == HttpMethod.PUT || 
config.getMethod() == HttpMethod.PATCH)) {
request.bodyValue(config.getBody());
}
return request;
}
private ValidationResult validateResponse(MonitorConfiguration config, 
String responseBody, 
long responseTime) {
ValidationResult result = new ValidationResult();
// Validate status code
if (config.getExpectedStatus() != null) {
// In real implementation, we would have the actual status code
// This is simplified for demonstration
result.addCheck("status_code", 
config.getExpectedStatus() == 200, 
"Expected status " + config.getExpectedStatus());
}
// Validate response body pattern
if (config.getExpectedBodyPattern() != null && responseBody != null) {
boolean matches = responseBody.contains(config.getExpectedBodyPattern());
result.addCheck("body_content", matches, 
"Body should contain: " + config.getExpectedBodyPattern());
}
// Validate response time
if (config.getValidationRules() != null) {
for (MonitorConfiguration.ValidationRule rule : config.getValidationRules()) {
if ("response_time".equals(rule.getType())) {
boolean passed = validateResponseTime(rule, responseTime);
result.addCheck("response_time_" + rule.getOperator(), passed, 
rule.getField() + " " + rule.getOperator() + " " + rule.getExpectedValue());
}
}
}
return result;
}
private boolean validateResponseTime(MonitorConfiguration.ValidationRule rule, long responseTime) {
long expected = Long.parseLong(rule.getExpectedValue());
return switch (rule.getOperator()) {
case "lt" -> responseTime < expected;
case "lte" -> responseTime <= expected;
case "gt" -> responseTime > expected;
case "gte" -> responseTime >= expected;
default -> false;
};
}
private ExecutionStatus determineErrorStatus(Exception e) {
if (e instanceof TimeoutException) {
return ExecutionStatus.TIMEOUT;
} else if (e instanceof WebClientResponseException) {
return ExecutionStatus.FAILED;
} else if (e instanceof SSLException) {
return ExecutionStatus.SSL_ERROR;
} else {
return ExecutionStatus.FAILED;
}
}
private MonitorConfiguration parseConfiguration(SyntheticMonitor monitor) throws Exception {
return objectMapper.readValue(monitor.getConfiguration(), MonitorConfiguration.class);
}
private void validateConfiguration(MonitorConfiguration config) {
if (config.getUrl() == null || config.getUrl().isBlank()) {
throw new IllegalArgumentException("URL is required for HTTP endpoint monitor");
}
if (config.getMethod() == null) {
config.setMethod(HttpMethod.GET);
}
}
private String truncateResponseBody(String responseBody) {
if (responseBody != null && responseBody.length() > 10000) {
return responseBody.substring(0, 10000) + "... [TRUNCATED]";
}
return responseBody;
}
private String toJson(Object obj) throws Exception {
return objectMapper.writeValueAsString(obj);
}
private String getStackTrace(Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
}
// ValidationResult.java
@Data
public class ValidationResult {
private boolean success = true;
private List<ValidationCheck> checks = new ArrayList<>();
private List<String> failures = new ArrayList<>();
public void addCheck(String name, boolean passed, String description) {
ValidationCheck check = new ValidationCheck(name, passed, description);
checks.add(check);
if (!passed) {
success = false;
failures.add(description);
}
}
@Data
@AllArgsConstructor
public static class ValidationCheck {
private String name;
private boolean passed;
private String description;
}
}
3. Browser Transaction Monitor
// BrowserTransactionExecutor.java
@Component
@MonitorExecutorType(MonitorType.BROWSER_TRANSACTION)
@Slf4j
public class BrowserTransactionExecutor implements MonitorExecutor {
private final WebDriverManager webDriverManager;
private final ObjectMapper objectMapper;
public BrowserTransactionExecutor(WebDriverManager webDriverManager,
ObjectMapper objectMapper) {
this.webDriverManager = webDriverManager;
this.objectMapper = objectMapper;
}
@Override
public MonitorExecution execute(SyntheticMonitor monitor) {
MonitorExecution execution = MonitorExecution.builder()
.monitor(monitor)
.region("default")
.startedAt(ZonedDateTime.now())
.status(ExecutionStatus.FAILED)
.build();
WebDriver driver = null;
try {
MonitorConfiguration config = parseConfiguration(monitor);
validateConfiguration(config);
driver = webDriverManager.createDriver();
configureDriver(driver, config);
long startTime = System.currentTimeMillis();
// Execute browser steps
executeBrowserSteps(driver, config, execution);
long totalTime = System.currentTimeMillis() - startTime;
execution.setResponseTimeMs(totalTime);
// Validate final state
ValidationResult validationResult = validateBrowserState(driver, config);
execution.setValidationResults(toJson(validationResult));
if (validationResult.isSuccess()) {
execution.setStatus(ExecutionStatus.SUCCESS);
} else {
execution.setStatus(ExecutionStatus.VALIDATION_FAILED);
execution.setErrorMessage("Browser validation failed");
}
// Capture screenshot if configured
if (config.getTakeScreenshot() != null && config.getTakeScreenshot()) {
captureScreenshot(driver, execution);
}
} catch (Exception e) {
log.error("Browser transaction execution failed for {}: {}", 
monitor.getName(), e.getMessage(), e);
execution.setErrorMessage(e.getMessage());
execution.setStackTrace(getStackTrace(e));
execution.setStatus(ExecutionStatus.BROWSER_ERROR);
} finally {
if (driver != null) {
driver.quit();
}
execution.setCompletedAt(ZonedDateTime.now());
}
return execution;
}
@Override
public boolean supports(MonitorType monitorType) {
return monitorType == MonitorType.BROWSER_TRANSACTION;
}
private void executeBrowserSteps(WebDriver driver, 
MonitorConfiguration config,
MonitorExecution execution) {
if (config.getBrowserSteps() == null) {
return;
}
for (int i = 0; i < config.getBrowserSteps().size(); i++) {
MonitorConfiguration.BrowserStep step = config.getBrowserSteps().get(i);
executeBrowserStep(driver, step, i, execution);
}
}
private void executeBrowserStep(WebDriver driver, 
MonitorConfiguration.BrowserStep step, 
int stepIndex,
MonitorExecution execution) {
try {
switch (step.getAction()) {
case "navigate":
driver.get(step.getValue());
break;
case "click":
WebElement clickElement = driver.findElement(By.cssSelector(step.getSelector()));
clickElement.click();
break;
case "type":
WebElement typeElement = driver.findElement(By.cssSelector(step.getSelector()));
typeElement.clear();
typeElement.sendKeys(step.getValue());
break;
case "wait":
Thread.sleep(Long.parseLong(step.getValue()));
break;
case "wait_for_element":
WebDriverWait wait = new WebDriverWait(driver, 
Duration.ofSeconds(step.getTimeoutSeconds() != null ? 
step.getTimeoutSeconds() : 10));
wait.until(ExpectedConditions.presenceOfElementLocated(
By.cssSelector(step.getSelector())));
break;
default:
log.warn("Unknown browser action: {}", step.getAction());
}
// Record step success
execution.getMetrics().put("step_" + stepIndex + "_success", "true");
} catch (Exception e) {
log.error("Browser step {} failed: {}", stepIndex, e.getMessage());
execution.getMetrics().put("step_" + stepIndex + "_success", "false");
execution.getMetrics().put("step_" + stepIndex + "_error", e.getMessage());
throw new RuntimeException("Step " + stepIndex + " failed: " + e.getMessage(), e);
}
}
private ValidationResult validateBrowserState(WebDriver driver, MonitorConfiguration config) {
ValidationResult result = new ValidationResult();
if (config.getValidationRules() != null) {
for (MonitorConfiguration.ValidationRule rule : config.getValidationRules()) {
try {
boolean passed = validateBrowserRule(driver, rule);
result.addCheck(rule.getType(), passed, rule.getField() + " " + rule.getOperator() + " " + rule.getExpectedValue());
} catch (Exception e) {
result.addCheck(rule.getType(), false, "Validation error: " + e.getMessage());
}
}
}
return result;
}
private boolean validateBrowserRule(WebDriver driver, MonitorConfiguration.ValidationRule rule) {
return switch (rule.getType()) {
case "url_contains" -> driver.getCurrentUrl().contains(rule.getExpectedValue());
case "title_equals" -> driver.getTitle().equals(rule.getExpectedValue());
case "element_present" -> !driver.findElements(By.cssSelector(rule.getField())).isEmpty();
case "element_text" -> {
WebElement element = driver.findElement(By.cssSelector(rule.getField()));
yield element.getText().contains(rule.getExpectedValue());
}
default -> false;
};
}
private void captureScreenshot(WebDriver driver, MonitorExecution execution) {
try {
File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
String screenshotPath = "screenshots/" + execution.getId() + ".png";
// Save screenshot to storage
execution.setScreenshotPath(screenshotPath);
} catch (Exception e) {
log.warn("Failed to capture screenshot: {}", e.getMessage());
}
}
private void configureDriver(WebDriver driver, MonitorConfiguration config) {
driver.manage().timeouts().pageLoadTimeout(
Duration.ofSeconds(config.getTimeoutSeconds() != null ? 
config.getTimeoutSeconds() : 30));
driver.manage().timeouts().implicitlyWait(
Duration.ofSeconds(10));
driver.manage().window().maximize();
}
private MonitorConfiguration parseConfiguration(SyntheticMonitor monitor) throws Exception {
return objectMapper.readValue(monitor.getConfiguration(), MonitorConfiguration.class);
}
private void validateConfiguration(MonitorConfiguration config) {
if (config.getBrowserSteps() == null || config.getBrowserSteps().isEmpty()) {
throw new IllegalArgumentException("Browser steps are required for browser transaction monitor");
}
}
private String toJson(Object obj) throws Exception {
return objectMapper.writeValueAsString(obj);
}
private String getStackTrace(Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
}
// WebDriverManager.java
@Component
@Slf4j
public class WebDriverManager {
@Value("${synthetic.monitoring.browser.headless:true}")
private boolean headless;
@Value("${synthetic.monitoring.browser.chrome-driver-path:}")
private String chromeDriverPath;
public WebDriver createDriver() {
ChromeOptions options = new ChromeOptions();
if (headless) {
options.addArguments("--headless");
}
options.addArguments("--no-sandbox");
options.addArguments("--disable-dev-shm-usage");
options.addArguments("--disable-gpu");
options.addArguments("--window-size=1920,1080");
// Set ChromeDriver path if provided
if (!chromeDriverPath.isBlank()) {
System.setProperty("webdriver.chrome.driver", chromeDriverPath);
}
try {
return new ChromeDriver(options);
} catch (Exception e) {
log.error("Failed to create ChromeDriver: {}", e.getMessage(), e);
throw new RuntimeException("Failed to create browser driver", e);
}
}
}

Scheduling and Execution Engine

1. Monitor Scheduler
// MonitorScheduler.java
@Component
@Slf4j
public class MonitorScheduler {
private final SyntheticMonitorRepository monitorRepository;
private final MonitorExecutorFactory executorFactory;
private final MonitorExecutionService executionService;
private final AlertService alertService;
private final TaskScheduler taskScheduler;
private final Map<Long, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
public MonitorScheduler(SyntheticMonitorRepository monitorRepository,
MonitorExecutorFactory executorFactory,
MonitorExecutionService executionService,
AlertService alertService,
TaskScheduler taskScheduler) {
this.monitorRepository = monitorRepository;
this.executorFactory = executorFactory;
this.executionService = executionService;
this.alertService = alertService;
this.taskScheduler = taskScheduler;
}
@PostConstruct
public void initializeSchedules() {
log.info("Initializing monitor schedules...");
List<SyntheticMonitor> enabledMonitors = monitorRepository.findByEnabledTrue();
for (SyntheticMonitor monitor : enabledMonitors) {
scheduleMonitor(monitor);
}
log.info("Scheduled {} monitors", enabledMonitors.size());
}
public void scheduleMonitor(SyntheticMonitor monitor) {
if (!monitor.isEnabled()) {
unscheduleMonitor(monitor.getId());
return;
}
// Unschedule existing task
unscheduleMonitor(monitor.getId());
// Create new task
Runnable monitorTask = createMonitorTask(monitor);
Trigger trigger = createTrigger(monitor.getFrequency());
ScheduledFuture<?> future = taskScheduler.schedule(monitorTask, trigger);
scheduledTasks.put(monitor.getId(), future);
log.info("Scheduled monitor '{}' with frequency {}", 
monitor.getName(), monitor.getFrequency());
}
public void unscheduleMonitor(Long monitorId) {
ScheduledFuture<?> future = scheduledTasks.remove(monitorId);
if (future != null) {
future.cancel(false);
log.info("Unscheduled monitor ID: {}", monitorId);
}
}
public void executeMonitorImmediately(Long monitorId) {
SyntheticMonitor monitor = monitorRepository.findById(monitorId)
.orElseThrow(() -> new IllegalArgumentException("Monitor not found: " + monitorId));
Runnable task = createMonitorTask(monitor);
task.run();
}
private Runnable createMonitorTask(SyntheticMonitor monitor) {
return () -> {
log.debug("Executing monitor: {}", monitor.getName());
try {
MonitorExecutor executor = executorFactory.getExecutor(monitor.getType());
MonitorExecution execution = executor.execute(monitor);
// Save execution result
executionService.saveExecution(execution);
// Check for alerts
alertService.checkAndTriggerAlerts(monitor, execution);
} catch (Exception e) {
log.error("Failed to execute monitor {}: {}", monitor.getName(), e.getMessage(), e);
}
};
}
private Trigger createTrigger(MonitorFrequency frequency) {
return context -> {
ZonedDateTime lastExecution = context.lastScheduledExecutionTime();
ZonedDateTime nextExecution = (lastExecution != null ? 
lastExecution : ZonedDateTime.now())
.plus(frequency.getDuration());
return nextExecution.toInstant();
};
}
public Map<Long, String> getScheduledMonitors() {
return scheduledTasks.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().isDone() ? "COMPLETED" : "SCHEDULED"
));
}
}
2. Execution Service
// MonitorExecutionService.java
@Service
@Slf4j
public class MonitorExecutionService {
private final MonitorExecutionRepository executionRepository;
private final MetricsService metricsService;
public MonitorExecutionService(MonitorExecutionRepository executionRepository,
MetricsService metricsService) {
this.executionRepository = executionRepository;
this.metricsService = metricsService;
}
@Transactional
public MonitorExecution saveExecution(MonitorExecution execution) {
MonitorExecution saved = executionRepository.save(execution);
// Update metrics
metricsService.recordExecutionMetrics(saved);
log.debug("Saved execution for monitor {}: status={}", 
execution.getMonitor().getName(), execution.getStatus());
return saved;
}
public List<MonitorExecution> getRecentExecutions(Long monitorId, int limit) {
return executionRepository.findByMonitorIdOrderByStartedAtDesc(monitorId, 
PageRequest.of(0, limit));
}
public MonitorMetrics getMonitorMetrics(Long monitorId, String region, 
ZonedDateTime from, ZonedDateTime to) {
List<MonitorExecution> executions = executionRepository
.findByMonitorIdAndRegionAndStartedAtBetween(monitorId, region, from, to);
return calculateMetrics(executions);
}
public AvailabilityReport getAvailabilityReport(Long monitorId, 
ZonedDateTime from, 
ZonedDateTime to) {
List<MonitorExecution> executions = executionRepository
.findByMonitorIdAndStartedAtBetween(monitorId, from, to);
return generateAvailabilityReport(executions);
}
private MonitorMetrics calculateMetrics(List<MonitorExecution> executions) {
if (executions.isEmpty()) {
return MonitorMetrics.builder()
.availability(0.0)
.totalExecutions(0)
.successfulExecutions(0)
.failedExecutions(0)
.build();
}
List<Long> responseTimes = executions.stream()
.filter(e -> e.getResponseTimeMs() != null)
.map(MonitorExecution::getResponseTimeMs)
.collect(Collectors.toList());
long successful = executions.stream()
.filter(MonitorExecution::isSuccessful)
.count();
double availability = (double) successful / executions.size() * 100;
// Calculate percentiles
Collections.sort(responseTimes);
double p50 = calculatePercentile(responseTimes, 50);
double p95 = calculatePercentile(responseTimes, 95);
double p99 = calculatePercentile(responseTimes, 99);
// Error breakdown
Map<ExecutionStatus, Integer> errorBreakdown = executions.stream()
.collect(Collectors.groupingBy(
MonitorExecution::getStatus,
Collectors.summingInt(e -> 1)
));
return MonitorMetrics.builder()
.responseTimeP50(p50)
.responseTimeP95(p95)
.responseTimeP99(p99)
.responseTimeMax(responseTimes.isEmpty() ? 0.0 : 
(double) responseTimes.get(responseTimes.size() - 1))
.availability(availability)
.totalExecutions(executions.size())
.successfulExecutions((int) successful)
.failedExecutions(executions.size() - (int) successful)
.errorBreakdown(errorBreakdown)
.build();
}
private double calculatePercentile(List<Long> values, double percentile) {
if (values.isEmpty()) return 0.0;
int index = (int) Math.ceil(percentile / 100.0 * values.size());
return values.get(Math.min(index - 1, values.size() - 1));
}
private AvailabilityReport generateAvailabilityReport(List<MonitorExecution> executions) {
// Implementation for detailed availability report
return new AvailabilityReport();
}
}

Alerting and Notification System

1. Alert Service
// AlertService.java
@Service
@Slf4j
public class AlertService {
private final MonitorExecutionRepository executionRepository;
private final NotificationService notificationService;
private final AlertRuleRepository alertRuleRepository;
private final Map<Long, AlertState> alertStates = new ConcurrentHashMap<>();
public AlertService(MonitorExecutionRepository executionRepository,
NotificationService notificationService,
AlertRuleRepository alertRuleRepository) {
this.executionRepository = executionRepository;
this.notificationService = notificationService;
this.alertRuleRepository = alertRuleRepository;
}
public void checkAndTriggerAlerts(SyntheticMonitor monitor, MonitorExecution execution) {
List<AlertRule> rules = alertRuleRepository.findByMonitorId(monitor.getId());
for (AlertRule rule : rules) {
if (shouldTriggerAlert(rule, execution)) {
triggerAlert(rule, execution);
}
}
}
private boolean shouldTriggerAlert(AlertRule rule, MonitorExecution execution) {
AlertState state = alertStates.computeIfAbsent(rule.getId(), 
id -> new AlertState(rule.getFailureThreshold(), rule.getRecoveryThreshold()));
boolean isFailure = !execution.isSuccessful();
state.recordResult(isFailure);
return state.shouldTriggerAlert();
}
private void triggerAlert(AlertRule rule, MonitorExecution execution) {
Alert alert = Alert.builder()
.rule(rule)
.monitor(execution.getMonitor())
.execution(execution)
.triggeredAt(ZonedDateTime.now())
.severity(rule.getSeverity())
.message(buildAlertMessage(rule, execution))
.build();
// Send notifications
notificationService.sendAlert(alert);
// Update alert state
AlertState state = alertStates.get(rule.getId());
state.markAlertTriggered();
log.warn("Alert triggered for monitor {}: {}", 
execution.getMonitor().getName(), alert.getMessage());
}
private String buildAlertMessage(AlertRule rule, MonitorExecution execution) {
return String.format(
"Alert: %s - Monitor '%s' failed with status %s. Error: %s",
rule.getName(),
execution.getMonitor().getName(),
execution.getStatus(),
execution.getErrorMessage()
);
}
}
// AlertState.java
@Data
public class AlertState {
private final int failureThreshold;
private final int recoveryThreshold;
private final Queue<Boolean> recentResults = new LinkedList<>();
private boolean alertActive = false;
private int consecutiveSuccesses = 0;
public AlertState(int failureThreshold, int recoveryThreshold) {
this.failureThreshold = failureThreshold;
this.recoveryThreshold = recoveryThreshold;
}
public void recordResult(boolean isFailure) {
recentResults.offer(isFailure);
// Keep only the last N results
while (recentResults.size() > failureThreshold) {
recentResults.poll();
}
if (isFailure) {
consecutiveSuccesses = 0;
} else {
consecutiveSuccesses++;
}
}
public boolean shouldTriggerAlert() {
if (alertActive) {
// Check for recovery
if (consecutiveSuccesses >= recoveryThreshold) {
alertActive = false;
// Trigger recovery notification
return true; // For recovery notification
}
return false;
} else {
// Check for failure
long failureCount = recentResults.stream().filter(f -> f).count();
if (failureCount >= failureThreshold) {
alertActive = true;
return true;
}
return false;
}
}
public void markAlertTriggered() {
// Reset state after triggering alert
recentResults.clear();
}
}
// Alert.java
@Entity
@Table(name = "alerts")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Alert {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "rule_id")
private AlertRule rule;
@ManyToOne
@JoinColumn(name = "monitor_id")
private SyntheticMonitor monitor;
@ManyToOne
@JoinColumn(name = "execution_id")
private MonitorExecution execution;
private ZonedDateTime triggeredAt;
private ZonedDateTime resolvedAt;
@Enumerated(EnumType.STRING)
private AlertSeverity severity;
private String message;
private boolean acknowledged = false;
public enum AlertSeverity {
CRITICAL, HIGH, MEDIUM, LOW, INFO
}
}
// AlertRule.java
@Entity
@Table(name = "alert_rules")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AlertRule {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "monitor_id")
private SyntheticMonitor monitor;
private String name;
private String description;
@Enumerated(EnumType.STRING)
private Alert.AlertSeverity severity;
private int failureThreshold = 3;
private int recoveryThreshold = 2;
private boolean enabled = true;
@ElementCollection
private List<String> notificationChannels = List.of("email", "slack");
}
2. Notification Service
// NotificationService.java
@Service
@Slf4j
public class NotificationService {
private final EmailService emailService;
private final SlackService slackService;
private final PagerDutyService pagerDutyService;
public NotificationService(EmailService emailService,
SlackService slackService,
PagerDutyService pagerDutyService) {
this.emailService = emailService;
this.slackService = slackService;
this.pagerDutyService = pagerDutyService;
}
public void sendAlert(Alert alert) {
for (String channel : alert.getRule().getNotificationChannels()) {
try {
switch (channel.toLowerCase()) {
case "email":
sendEmailAlert(alert);
break;
case "slack":
sendSlackAlert(alert);
break;
case "pagerduty":
sendPagerDutyAlert(alert);
break;
default:
log.warn("Unknown notification channel: {}", channel);
}
} catch (Exception e) {
log.error("Failed to send alert via {}: {}", channel, e.getMessage(), e);
}
}
}
private void sendEmailAlert(Alert alert) {
String subject = String.format("[%s] Alert: %s", 
alert.getSeverity(), alert.getMonitor().getName());
String body = buildEmailBody(alert);
emailService.sendEmail(
Arrays.asList("[email protected]", "[email protected]"),
subject,
body
);
}
private void sendSlackAlert(Alert alert) {
String message = buildSlackMessage(alert);
slackService.sendMessage("#alerts", message);
}
private void sendPagerDutyAlert(Alert alert) {
if (alert.getSeverity() == Alert.AlertSeverity.CRITICAL) {
pagerDutyService.triggerIncident(
alert.getMonitor().getName() + " - " + alert.getMessage(),
"Synthetic Monitor",
IncidentSeverity.CRITICAL
);
}
}
private String buildEmailBody(Alert alert) {
return String.format("""
Alert Details:
=============
Monitor: %s
Severity: %s
Triggered: %s
Error: %s
Execution ID: %s
Monitor Configuration:
====================
Type: %s
Frequency: %s
URL: %s
Recent Executions:
=================
Check the monitoring dashboard for detailed execution history.
This is an automated alert from Synthetic Monitoring System.
""",
alert.getMonitor().getName(),
alert.getSeverity(),
alert.getTriggeredAt(),
alert.getMessage(),
alert.getExecution().getId(),
alert.getMonitor().getType(),
alert.getMonitor().getFrequency(),
extractUrlFromConfig(alert.getMonitor())
);
}
private String buildSlackMessage(Alert alert) {
return String.format("""
:warning: *%s Alert: %s*
> %s
_Triggered: %s_
<https://monitoring.company.com/alerts/%s|View Details>
""",
alert.getSeverity(),
alert.getMonitor().getName(),
alert.getMessage(),
alert.getTriggeredAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
alert.getId()
);
}
private String extractUrlFromConfig(SyntheticMonitor monitor) {
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode config = mapper.readTree(monitor.getConfiguration());
return config.path("url").asText("N/A");
} catch (Exception e) {
return "N/A";
}
}
}

REST API Controllers

1. Monitor Management API
// MonitorController.java
@RestController
@RequestMapping("/api/v1/monitors")
@Slf4j
@Validated
public class MonitorController {
private final SyntheticMonitorRepository monitorRepository;
private final MonitorScheduler scheduler;
private final MonitorExecutionService executionService;
public MonitorController(SyntheticMonitorRepository monitorRepository,
MonitorScheduler scheduler,
MonitorExecutionService executionService) {
this.monitorRepository = monitorRepository;
this.scheduler = scheduler;
this.executionService = executionService;
}
@GetMapping
public ResponseEntity<List<SyntheticMonitor>> listMonitors(
@RequestParam(required = false) Boolean enabled) {
List<SyntheticMonitor> monitors;
if (enabled != null) {
monitors = monitorRepository.findByEnabled(enabled);
} else {
monitors = monitorRepository.findAll();
}
return ResponseEntity.ok(monitors);
}
@GetMapping("/{id}")
public ResponseEntity<SyntheticMonitor> getMonitor(@PathVariable Long id) {
return monitorRepository.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<SyntheticMonitor> createMonitor(
@Valid @RequestBody SyntheticMonitor monitor) {
SyntheticMonitor saved = monitorRepository.save(monitor);
// Schedule the monitor
scheduler.scheduleMonitor(saved);
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}
@PutMapping("/{id}")
public ResponseEntity<SyntheticMonitor> updateMonitor(
@PathVariable Long id,
@Valid @RequestBody SyntheticMonitor monitor) {
if (!monitorRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
monitor.setId(id);
SyntheticMonitor saved = monitorRepository.save(monitor);
// Reschedule the monitor
scheduler.scheduleMonitor(saved);
return ResponseEntity.ok(saved);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteMonitor(@PathVariable Long id) {
if (!monitorRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
// Unschedule first
scheduler.unscheduleMonitor(id);
monitorRepository.deleteById(id);
return ResponseEntity.noContent().build();
}
@PostMapping("/{id}/execute")
public ResponseEntity<MonitorExecution> executeMonitor(@PathVariable Long id) {
try {
scheduler.executeMonitorImmediately(id);
return ResponseEntity.accepted().build();
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@GetMapping("/{id}/executions")
public ResponseEntity<List<MonitorExecution>> getMonitorExecutions(
@PathVariable Long id,
@RequestParam(defaultValue = "10") int limit) {
List<MonitorExecution> executions = executionService.getRecentExecutions(id, limit);
return ResponseEntity.ok(executions);
}
@GetMapping("/{id}/metrics")
public ResponseEntity<MonitorMetrics> getMonitorMetrics(
@PathVariable Long id,
@RequestParam String region,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) ZonedDateTime from,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) ZonedDateTime to) {
MonitorMetrics metrics = executionService.getMonitorMetrics(id, region, from, to);
return ResponseEntity.ok(metrics);
}
}
2. Dashboard and Metrics API
// DashboardController.java
@RestController
@RequestMapping("/api/v1/dashboard")
@Slf4j
public class DashboardController {
private final SyntheticMonitorRepository monitorRepository;
private final MonitorExecutionService executionService;
private final MetricsService metricsService;
public DashboardController(SyntheticMonitorRepository monitorRepository,
MonitorExecutionService executionService,
MetricsService metricsService) {
this.monitorRepository = monitorRepository;
this.executionService = executionService;
this.metricsService = metricsService;
}
@GetMapping("/summary")
public ResponseEntity<DashboardSummary> getDashboardSummary() {
List<SyntheticMonitor> monitors = monitorRepository.findAll();
long totalMonitors = monitors.size();
long enabledMonitors = monitors.stream().filter(SyntheticMonitor::isEnabled).count();
// Calculate overall availability (simplified)
double overallAvailability = calculateOverallAvailability();
// Get recent alerts count
long activeAlerts = getActiveAlertsCount();
DashboardSummary summary = DashboardSummary.builder()
.totalMonitors(totalMonitors)
.enabledMonitors(enabledMonitors)
.overallAvailability(overallAvailability)
.activeAlerts(activeAlerts)
.lastUpdated(ZonedDateTime.now())
.build();
return ResponseEntity.ok(summary);
}
@GetMapping("/monitors/status")
public ResponseEntity<List<MonitorStatus>> getMonitorsStatus() {
List<SyntheticMonitor> monitors = monitorRepository.findAll();
List<MonitorStatus> statusList = new ArrayList<>();
for (SyntheticMonitor monitor : monitors) {
MonitorStatus status = getMonitorStatus(monitor);
statusList.add(status);
}
return ResponseEntity.ok(statusList);
}
private MonitorStatus getMonitorStatus(SyntheticMonitor monitor) {
List<MonitorExecution> recentExecutions = executionService
.getRecentExecutions(monitor.getId(), 10);
boolean isHealthy = recentExecutions.stream()
.allMatch(MonitorExecution::isSuccessful);
double recentAvailability = recentExecutions.stream()
.filter(MonitorExecution::isSuccessful)
.count() / (double) recentExecutions.size() * 100;
double avgResponseTime = recentExecutions.stream()
.filter(e -> e.getResponseTimeMs() != null)
.mapToLong(MonitorExecution::getResponseTimeMs)
.average()
.orElse(0.0);
return MonitorStatus.builder()
.monitorId(monitor.getId())
.monitorName(monitor.getName())
.type(monitor.getType())
.enabled(monitor.isEnabled())
.healthy(isHealthy)
.availability(recentAvailability)
.averageResponseTime(avgResponseTime)
.lastExecution(recentExecutions.isEmpty() ? null : 
recentExecutions.get(0).getStartedAt())
.lastStatus(recentExecutions.isEmpty() ? null : 
recentExecutions.get(0).getStatus())
.build();
}
private double calculateOverallAvailability() {
// Simplified implementation
List<SyntheticMonitor> monitors = monitorRepository.findAll();
double totalAvailability = 0;
int count = 0;
for (SyntheticMonitor monitor : monitors) {
if (monitor.isEnabled()) {
// Get recent availability for each monitor
List<MonitorExecution> recentExecutions = executionService
.getRecentExecutions(monitor.getId(), 5);
if (!recentExecutions.isEmpty()) {
double availability = recentExecutions.stream()
.filter(MonitorExecution::isSuccessful)
.count() / (double) recentExecutions.size() * 100;
totalAvailability += availability;
count++;
}
}
}
return count > 0 ? totalAvailability / count : 100.0;
}
private long getActiveAlertsCount() {
// Implementation would query active alerts from database
return 0L;
}
}
// DashboardSummary.java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DashboardSummary {
private long totalMonitors;
private long enabledMonitors;
private double overallAvailability;
private long activeAlerts;
private ZonedDateTime lastUpdated;
}
// MonitorStatus.java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MonitorStatus {
private Long monitorId;
private String monitorName;
private MonitorType type;
private boolean enabled;
private boolean healthy;
private double availability;
private double averageResponseTime;
private ZonedDateTime lastExecution;
private ExecutionStatus lastStatus;
}

Configuration

1. Application Properties
# application.yml
synthetic:
monitoring:
enabled: true
default-timeout-seconds: 30
max-concurrent-monitors: 10
default-user-agent: "SyntheticMonitor/1.0"
default-regions:
- us-east-1
- eu-west-1
- ap-southeast-1
alert:
enabled: true
failure-threshold: 3
recovery-threshold: 2
notification-channels:
- email
- slack
pager-duty-service-key: ${PAGERDUTY_SERVICE_KEY:}
retention:
results-days: 30
metrics-days: 90
traces-days: 7
browser:
headless: true
page-load-timeout-seconds: 30
script-timeout-seconds: 10
chrome-driver-path: ${CHROME_DRIVER_PATH:}
browser-args:
- "--no-sandbox"
- "--disable-dev-shm-usage"
# Database
spring:
datasource:
url: jdbc:h2:file:./data/synthetic-monitoring
driverClassName: org.h2.Driver
username: sa
password: password
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: update
show-sql: false
h2:
console:
enabled: true
path: /h2-console
# Email
spring:
mail:
host: smtp.company.com
port: 587
username: ${SMTP_USERNAME:}
password: ${SMTP_PASSWORD:}
properties:
mail:
smtp:
auth: true
starttls:
enable: true
# Slack
slack:
webhook-url: ${SLACK_WEBHOOK_URL:}
# Logging
logging:
level:
com.company.synthetic: INFO
file:
name: logs/synthetic-monitoring.log
2. Spring Configuration
// SyntheticMonitoringConfig.java
@Configuration
@EnableScheduling
@EnableAsync
@EnableConfigurationProperties(MonitorConfig.class)
public class SyntheticMonitoringConfig {
@Bean
@Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
@Bean
public WebClient webClient(WebClient.Builder builder) {
return builder
.defaultHeader("User-Agent", "SyntheticMonitor/1.0")
.build();
}
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("synthetic-monitor-");
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(30);
return scheduler;
}
@Bean
public MeterRegistry meterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
}

This comprehensive synthetic monitoring system provides:

  • Multiple monitor types (HTTP, Browser, API sequences, etc.)
  • Flexible scheduling with configurable frequencies
  • Comprehensive alerting with threshold-based triggers
  • Multi-channel notifications (Email, Slack, PagerDuty)
  • Detailed metrics and reporting
  • REST API for management and integration
  • Browser automation for complex user journey monitoring
  • Extensible architecture for adding new monitor types

The system is production-ready and can be deployed across multiple regions for comprehensive application monitoring.

Leave a Reply

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


Macro Nepal Helper