Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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:
Expand Down
Empty file modified mvnw
100644 → 100755
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -14,7 +15,7 @@
* <p>
* 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
*/
Expand Down Expand Up @@ -66,12 +67,20 @@ public FilterRegistrationBean<LogDispatchFilter> 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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> maskedHeaders = List.of();
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
}
Loading