Skip to content

Commit 671e50c

Browse files
committed
Add README with install, quick start, configuration, and features
1 parent 4d4e0c6 commit 671e50c

1 file changed

Lines changed: 191 additions & 0 deletions

File tree

README.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# PeekAPI — Java SDK
2+
3+
[![Maven Central](https://img.shields.io/maven-central/v/dev.peekapi/peekapi)](https://central.sonatype.com/artifact/dev.peekapi/peekapi)
4+
[![license](https://img.shields.io/github/license/peekapi-dev/sdk-java)](./LICENSE)
5+
[![CI](https://github.com/peekapi-dev/sdk-java/actions/workflows/ci.yml/badge.svg)](https://github.com/peekapi-dev/sdk-java/actions/workflows/ci.yml)
6+
7+
Zero-dependency Java SDK for [PeekAPI](https://peekapi.dev). Jakarta Servlet Filter for any servlet container, with Spring Boot auto-configuration via `application.properties`.
8+
9+
## Install
10+
11+
**Gradle (Kotlin DSL):**
12+
13+
```kotlin
14+
dependencies {
15+
implementation("dev.peekapi:peekapi:0.1.0")
16+
}
17+
```
18+
19+
**Maven:**
20+
21+
```xml
22+
<dependency>
23+
<groupId>dev.peekapi</groupId>
24+
<artifactId>peekapi</artifactId>
25+
<version>0.1.0</version>
26+
</dependency>
27+
```
28+
29+
## Quick Start
30+
31+
### Spring Boot (auto-configuration)
32+
33+
Add your API key to `application.properties` — the filter registers automatically on all routes:
34+
35+
```properties
36+
peekapi.api-key=ak_live_xxx
37+
```
38+
39+
Optional properties:
40+
41+
```properties
42+
peekapi.endpoint=https://ingest.peekapi.dev/v1/events
43+
peekapi.debug=false
44+
peekapi.collect-query-string=false
45+
peekapi.flush-interval-seconds=15
46+
peekapi.batch-size=250
47+
```
48+
49+
### Spring Boot (programmatic)
50+
51+
Register the filter bean manually for full control over options:
52+
53+
```java
54+
import dev.peekapi.PeekApiClient;
55+
import dev.peekapi.PeekApiOptions;
56+
import dev.peekapi.middleware.PeekApiFilter;
57+
import org.springframework.boot.web.servlet.FilterRegistrationBean;
58+
import org.springframework.context.annotation.Bean;
59+
import org.springframework.context.annotation.Configuration;
60+
61+
@Configuration
62+
public class PeekApiConfig {
63+
64+
@Bean
65+
public PeekApiClient peekApiClient() {
66+
PeekApiOptions options = PeekApiOptions.builder("ak_live_xxx")
67+
.debug(true)
68+
.build();
69+
return new PeekApiClient(options);
70+
}
71+
72+
@Bean
73+
public FilterRegistrationBean<PeekApiFilter> peekApiFilter(PeekApiClient client) {
74+
PeekApiOptions options = PeekApiOptions.builder("ak_live_xxx").build();
75+
FilterRegistrationBean<PeekApiFilter> reg = new FilterRegistrationBean<>();
76+
reg.setFilter(new PeekApiFilter(client, options));
77+
reg.addUrlPatterns("/*");
78+
return reg;
79+
}
80+
}
81+
```
82+
83+
### Servlet Container (web.xml)
84+
85+
```xml
86+
<filter>
87+
<filter-name>peekapi</filter-name>
88+
<filter-class>dev.peekapi.middleware.PeekApiFilter</filter-class>
89+
<init-param>
90+
<param-name>apiKey</param-name>
91+
<param-value>ak_live_xxx</param-value>
92+
</init-param>
93+
</filter>
94+
<filter-mapping>
95+
<filter-name>peekapi</filter-name>
96+
<url-pattern>/*</url-pattern>
97+
</filter-mapping>
98+
```
99+
100+
### Standalone Client
101+
102+
```java
103+
import dev.peekapi.PeekApiClient;
104+
import dev.peekapi.PeekApiOptions;
105+
import dev.peekapi.RequestEvent;
106+
107+
PeekApiOptions options = PeekApiOptions.builder("ak_live_xxx").build();
108+
PeekApiClient client = new PeekApiClient(options);
109+
110+
client.track(new RequestEvent("GET", "/api/users", 200, 42.0, 0, 1024, "consumer_123", null));
111+
112+
// Graceful shutdown (flushes remaining events)
113+
client.shutdown();
114+
```
115+
116+
## Configuration
117+
118+
| Builder method | Type | Default | Description |
119+
|---|---|---|---|
120+
| `apiKey` (required) | `String` || Your PeekAPI API key |
121+
| `endpoint` | `String` | `https://ingest.peekapi.dev/v1/events` | Ingestion endpoint URL |
122+
| `flushInterval` | `Duration` | `15s` | Time between automatic flushes |
123+
| `batchSize` | `int` | `250` | Events per batch (triggers flush when reached) |
124+
| `maxBufferSize` | `int` | `10,000` | Maximum events held in memory |
125+
| `maxStorageBytes` | `long` | `5 MB` | Maximum disk fallback file size |
126+
| `maxEventBytes` | `int` | `64 KB` | Per-event size limit (drops if exceeded) |
127+
| `storagePath` | `String` | temp dir | Path for JSONL fallback file |
128+
| `debug` | `boolean` | `false` | Enable debug logging to stderr |
129+
| `collectQueryString` | `boolean` | `false` | Include sorted query params in path |
130+
| `identifyConsumer` | `Function<HttpServletRequest, String>` | auto | Custom consumer identification callback |
131+
| `onError` | `Consumer<Exception>` | `null` | Callback for background flush errors |
132+
| `sslContext` | `SSLContext` | `null` | Custom TLS/SSL configuration |
133+
134+
## How It Works
135+
136+
1. Your application handles requests through the servlet filter (or you call `client.track()` directly)
137+
2. The filter captures method, path, status code, response time, request/response size, and consumer ID
138+
3. Events are buffered in memory and flushed every 15 seconds (or when `batchSize` is reached) via a daemon thread backed by `ScheduledExecutorService`
139+
4. Batches are sent as JSON arrays to the PeekAPI ingest endpoint using `java.net.http.HttpClient`
140+
5. On failure, events are re-inserted into the buffer with exponential backoff (up to 5 retries)
141+
6. After max retries, events are persisted to a JSONL file on disk and recovered on the next flush cycle
142+
7. On JVM shutdown, a shutdown hook flushes remaining events and persists any leftovers to disk
143+
144+
## Consumer Identification
145+
146+
By default, consumers are identified by:
147+
148+
1. `X-API-Key` header — stored as-is
149+
2. `Authorization` header — hashed with SHA-256 (stored as `hash_<hex>`)
150+
151+
Override with the `identifyConsumer` option to use any header or request attribute:
152+
153+
```java
154+
PeekApiOptions options = PeekApiOptions.builder("ak_live_xxx")
155+
.identifyConsumer(req -> req.getHeader("X-Tenant-ID"))
156+
.build();
157+
```
158+
159+
The callback receives an `HttpServletRequest` and should return a consumer ID string or `null`.
160+
161+
## Features
162+
163+
- **Zero runtime dependencies** — built on `java.net.http` and `java.security` (JDK 17+)
164+
- **Jakarta Servlet Filter** — drop-in for any servlet container (Tomcat, Jetty, Undertow)
165+
- **Spring Boot auto-configuration** — just set `peekapi.api-key` in `application.properties`
166+
- **Background flush** — daemon thread via `ScheduledExecutorService`, never blocks your requests
167+
- **Disk persistence** — undelivered events saved to JSONL, recovered automatically on next startup
168+
- **Exponential backoff** — retries with jitter on transient failures, gives up after 5 consecutive errors
169+
- **SSRF protection** — validates endpoint host against private IP ranges at construction time
170+
- **Input sanitization** — path (2048), method (16), consumer ID (256) character limits enforced
171+
- **Per-event size limit** — events exceeding `maxEventBytes` are dropped (metadata stripped first)
172+
- **Graceful shutdown** — JVM shutdown hook flushes buffer and persists remaining events to disk
173+
174+
## Requirements
175+
176+
- Java >= 17
177+
- Jakarta Servlet API >= 6.0 (provided by your servlet container or Spring Boot 3.x)
178+
179+
## Contributing
180+
181+
Bug reports and feature requests: [peekapi-dev/community](https://github.com/peekapi-dev/community/issues)
182+
183+
1. Fork & clone the repo
184+
2. Run tests — `./gradlew test`
185+
3. Lint — `./gradlew spotlessCheck`
186+
4. Format — `./gradlew spotlessApply`
187+
5. Submit a PR
188+
189+
## License
190+
191+
MIT

0 commit comments

Comments
 (0)