diff --git a/README.md b/README.md index 3b694ee..5383d04 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ logdispatch.exclude-paths=/health,/actuator/**,/metrics/** | `logdispatch.masked-headers` | ❌ No | Comma-separated list of headers to mask. Defaults to none | | `logdispatch.exclude-paths` | ❌ No | Comma-separated list of URI paths to exclude. Supports wildcards such as `/actuator/**` | | `logdispatch.timeout-ms` | ❌ No | Connection and read timeout in milliseconds. Defaults to `3000`. | +̉| `logdispatch.health.enabled` | ❌ No | Enables or disables the `/logdispatch/health` endpoint. Defaults to `true` | Disable LogDispatch in local or test profiles when you want the dependency on the classpath but do not want any APM activity: @@ -186,6 +187,52 @@ GET /logdispatch/health } ``` +### Disabling the Health Endpoint + +If your application uses a security layer (e.g. Spring Security, an API gateway) that requires all unauthenticated endpoints to be explicitly opted in, or you simply don't want the endpoint exposed, disable it entirely: + +```yaml +logdispatch: + health: + enabled: false +``` + +```properties +logdispatch.health.enabled=false +``` + +When disabled, the `/logdispatch/health` endpoint is not registered at all — requests to that path receive a `404 Not Found`, the same as any other undefined route. + +### Rate Limiting + +To prevent abuse, the endpoint is limited to: + +```text +60 requests per minute per IP +``` + +Requests exceeding the limit receive: + +```http +429 Too Many Requests +``` + +### Endpoint + +```http +GET /logdispatch/health +``` + +### Response + +```json +{ + "status": "UP", + "startupTime": "2026-05-31T02:00:00.000Z", + "uptimeSeconds": 120 +} +``` + ### Rate Limiting To prevent abuse, the endpoint is limited to: diff --git a/mvnw b/mvnw old mode 100644 new mode 100755 diff --git a/src/main/java/in/maheshlangote/logdispatch/config/LogDispatchAutoConfiguration.java b/src/main/java/in/maheshlangote/logdispatch/config/LogDispatchAutoConfiguration.java index c543bd8..47c8b0a 100644 --- a/src/main/java/in/maheshlangote/logdispatch/config/LogDispatchAutoConfiguration.java +++ b/src/main/java/in/maheshlangote/logdispatch/config/LogDispatchAutoConfiguration.java @@ -4,6 +4,7 @@ import in.maheshlangote.logdispatch.LogDispatchFilter; import in.maheshlangote.logdispatch.LogDispatchHealthController; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; @@ -14,7 +15,7 @@ *

* This configuration automatically registers LogDispatch beans and passes the * {@code logdispatch.enabled} flag to each component so disabled mode can no-op. - * + * * @author Mahesh Langote * @version 1.0.0 */ @@ -66,12 +67,20 @@ public FilterRegistrationBean logDispatchFilterRegistration(L /** * Creates and exposes the {@link in.maheshlangote.logdispatch.LogDispatchHealthController} bean. * This controller provides a lightweight health endpoint for the APM server to poll. + * Registration is skipped entirely when {@code logdispatch.health.enabled=false}, + * so the endpoint does not exist rather than responding with a "disabled" status. * * @param properties LogDispatch configuration properties * @return a fully configured {@link in.maheshlangote.logdispatch.LogDispatchHealthController}. */ @Bean + @ConditionalOnProperty( + prefix = "logdispatch.health", + name = "enabled", + havingValue = "true", + matchIfMissing = true + ) public LogDispatchHealthController logDispatchHealthController(LogDispatchProperties properties) { return new LogDispatchHealthController(properties.isEnabled()); } -} +} \ No newline at end of file diff --git a/src/main/java/in/maheshlangote/logdispatch/config/LogDispatchProperties.java b/src/main/java/in/maheshlangote/logdispatch/config/LogDispatchProperties.java index e8f4276..6d17fac 100644 --- a/src/main/java/in/maheshlangote/logdispatch/config/LogDispatchProperties.java +++ b/src/main/java/in/maheshlangote/logdispatch/config/LogDispatchProperties.java @@ -11,6 +11,7 @@ public class LogDispatchProperties { private boolean enabled = true; + private Health health = new Health(); private String serverUrl = "http://localhost:8081/api/v1/ingest/logs"; private String apiKey = "default-key"; private List maskedHeaders = List.of(); @@ -22,6 +23,36 @@ public class LogDispatchProperties { * * @return {@code true} when LogDispatch should inspect and dispatch errors */ + + public Health getHealth(){ + return health; + } + + public void setHealth(Health health){ + this.health = health; + } + + /** + * Configuration for the LogDispatch health endpoint. + * */ + public static class Health { + private boolean enabled = true; + /** + * Returns whether the LogDispatch health endpoint is enabled. + * + * @return {@code true} when the /logdispatch/health endpoint should be registered + */ + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled){ + this.enabled = enabled; + } + } + + + public boolean isEnabled() { return enabled; } diff --git a/src/test/java/in/maheshlangote/logdispatch/config/LogDispatchAutoConfigurationTest.java b/src/test/java/in/maheshlangote/logdispatch/config/LogDispatchAutoConfigurationTest.java index 20c65d7..729583a 100644 --- a/src/test/java/in/maheshlangote/logdispatch/config/LogDispatchAutoConfigurationTest.java +++ b/src/test/java/in/maheshlangote/logdispatch/config/LogDispatchAutoConfigurationTest.java @@ -32,4 +32,20 @@ void shouldRegisterBeansWithDisabledProperties() { assertThat(context.getBean(LogDispatchProperties.class).isEnabled()).isFalse(); }); } + @Test + void shouldNotRegisterHealthControllerWhenHealthDisabled() { + contextRunner + .withPropertyValues("logdispatch.health.enabled=false") + .run(context -> { + assertThat(context).doesNotHaveBean(LogDispatchHealthController.class); + }); +} + +@Test +void shouldRegisterHealthControllerByDefaultWhenHealthPropertyMissing() { + contextRunner + .run(context -> { + assertThat(context).hasSingleBean(LogDispatchHealthController.class); + }); +} }