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
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,22 @@ metrics.set("memory_usage_mb", 512.5);
metrics.set("queue_size", 0);
```

## Apps

A domain has one ingest key, the domain key, shared by all apps of that domain, such as a desktop app, an API and a landing page.
Set `app` to name this app, so the dashboard can filter logs and metrics by app:

```java
var logdash = Logdash.builder()
.apiKey(System.getenv("LOGDASH_API_KEY")) // the domain key
.app("api")
.build();
```

Every log and metric this instance sends carries `"app": "api"`.
With a domain key, the app is created on first use.
Older service keys ignore `app`.

## View Your Data

To see the logs or metrics, go to your project dashboard at [logdash.io](https://logdash.io/app/clusters)
Expand Down Expand Up @@ -315,20 +331,19 @@ Logdash logdash = Logdash.builder()
.build();
```

## Configuration

### All Configuration Options

| Parameter | Default | Description |
|-------------------------|--------------------------|----------------------------------|
| `apiKey` | `null` | Your Logdash API key (required) |
| `app` | `null` | App name sent with logs/metrics |
| `baseUrl` | `https://api.logdash.io` | Logdash API endpoint |
| `enableConsoleOutput` | `true` | Show logs/metrics in console |
| `enableVerboseLogging` | `false` | Enable SDK debug output |
| `requestTimeoutMs` | `15000` | HTTP request timeout |
| `maxRetries` | `3` | Failed request retry attempts |
| `retryDelayMs` | `500` | Base delay between retries |
| `maxConcurrentRequests` | `10` | Maximum concurrent HTTP requests |
| `maxConcurrentRequests` | `20` | Maximum concurrent HTTP requests |
| `shutdownTimeoutMs` | `10000` | Graceful shutdown timeout |

## Performance Considerations
Expand All @@ -337,7 +352,6 @@ Logdash logdash = Logdash.builder()
- **Use structured logging** for better searchability and performance
- **Avoid logging sensitive data** (passwords, tokens, PII)
- **The SDK is async** - logging calls return immediately without blocking
- **Memory efficient** - automatic batching and resource management

## Getting Your API Key

Expand Down
29 changes: 28 additions & 1 deletion src/main/java/io/logdash/sdk/Logdash.java
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ public static final class Builder {
private long requestTimeoutMs = 15000L;
private long shutdownTimeoutMs = 10000L;
private int maxConcurrentRequests = 20;
private String app;

private Builder() {
}
Expand Down Expand Up @@ -342,6 +343,31 @@ public Builder maxConcurrentRequests(int maxConcurrentRequests) {
return this;
}

/**
* Sets the name of the app that sends the data, such as {@code desktop}, {@code api} or
* {@code landing}.
*
* <p>The domain key is shared by all apps of a domain, and {@code app} names this one, so the
* dashboard can filter logs and metrics by app. Every log and metric this instance sends
* carries it. With a domain key, the app is created on first use. Older service keys ignore
* {@code app}.
*
* <pre>{@code
* var logdash = Logdash.builder()
* .apiKey(System.getenv("LOGDASH_API_KEY"))
* .app("api")
* .build();
* }</pre>
*
* @param app the app name (defaults to none; blank means none; the server cuts it to 64
* characters)
* @return this builder
*/
public Builder app(String app) {
this.app = app;
return this;
}

/**
* Builds the Logdash instance with the configured settings.
*
Expand All @@ -366,7 +392,8 @@ public Logdash build() {
retryDelayMs,
requestTimeoutMs,
shutdownTimeoutMs,
maxConcurrentRequests);
maxConcurrentRequests,
app);
return new Logdash(config);
}
}
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/io/logdash/sdk/config/LogdashConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
* @param requestTimeoutMs HTTP request timeout in milliseconds
* @param shutdownTimeoutMs Maximum time to wait for graceful shutdown
* @param maxConcurrentRequests Maximum number of concurrent HTTP requests
* @param app Name of the app sent with every log and metric, trimmed; null when
* not set or blank
*/
public record LogdashConfig(
String apiKey,
Expand All @@ -30,7 +32,8 @@ public record LogdashConfig(
long retryDelayMs,
long requestTimeoutMs,
long shutdownTimeoutMs,
int maxConcurrentRequests) {
int maxConcurrentRequests,
String app) {
private static final int MIN_API_KEY_LENGTH = 3;
private static final int MAX_API_KEY_LENGTH = 256;

Expand All @@ -43,6 +46,7 @@ public record LogdashConfig(
requestTimeoutMs,
shutdownTimeoutMs,
maxConcurrentRequests);
app = app == null || app.isBlank() ? null : app.strip();
}

/**
Expand Down Expand Up @@ -120,6 +124,7 @@ public static final class Builder {
private long requestTimeoutMs = 15000L;
private long shutdownTimeoutMs = 10000L;
private int maxConcurrentRequests = 10;
private String app = null;

private Builder() {
}
Expand Down Expand Up @@ -169,6 +174,11 @@ public Builder maxConcurrentRequests(int maxConcurrentRequests) {
return this;
}

public Builder app(String app) {
this.app = app;
return this;
}

/**
* Builds the LogdashConfig instance with the configured settings.
*
Expand All @@ -185,7 +195,8 @@ public LogdashConfig build() {
retryDelayMs,
requestTimeoutMs,
shutdownTimeoutMs,
maxConcurrentRequests);
maxConcurrentRequests,
app);
}
}
}
2 changes: 1 addition & 1 deletion src/main/java/io/logdash/sdk/transport/HttpTransport.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public final class HttpTransport implements LogdashTransport, AutoCloseable {
*/
public HttpTransport(LogdashConfig config) {
this.config = config;
this.serializer = new JsonSerializer();
this.serializer = new JsonSerializer(config.app());
this.rateLimiter = new Semaphore(config.maxConcurrentRequests(), true);
this.executorService = createOptimizedExecutorService();
this.httpClient = createOptimizedHttpClient();
Expand Down
45 changes: 36 additions & 9 deletions src/main/java/io/logdash/sdk/util/JsonSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.logdash.sdk.log.LogEntry;
import io.logdash.sdk.metrics.MetricEntry;

import java.util.HashMap;
import java.util.Map;

/**
Expand All @@ -25,6 +26,24 @@
public final class JsonSerializer {
private static final ObjectMapper OBJECT_MAPPER = createObjectMapper();

private final String app;

/**
* Creates a serializer that sends no app.
*/
public JsonSerializer() {
this(null);
}

/**
* Creates a serializer that adds the app to every log and metric.
*
* @param app the app name, or null to leave the {@code app} key out
*/
public JsonSerializer(String app) {
this.app = app;
}

private static ObjectMapper createObjectMapper() {
return JsonMapper.builder()
.addModule(new JavaTimeModule())
Expand All @@ -44,11 +63,15 @@ private static ObjectMapper createObjectMapper() {
public String serialize(LogEntry logEntry) {
try {
var logData =
Map.of(
"message", sanitizeString(logEntry.message()),
"level", logEntry.level().getValue(),
"createdAt", logEntry.createdAt(),
"sequenceNumber", logEntry.sequenceNumber());
new HashMap<String, Object>(
Map.of(
"message", sanitizeString(logEntry.message()),
"level", logEntry.level().getValue(),
"createdAt", logEntry.createdAt(),
"sequenceNumber", logEntry.sequenceNumber()));
if (app != null) {
logData.put("app", app);
}
return OBJECT_MAPPER.writeValueAsString(logData);
} catch (JsonProcessingException e) {
return createFallbackLogJson(logEntry, e);
Expand All @@ -64,10 +87,14 @@ public String serialize(LogEntry logEntry) {
public String serialize(MetricEntry metricEntry) {
try {
var metricData =
Map.of(
"name", sanitizeString(metricEntry.name()),
"value", sanitizeNumber(metricEntry.value()),
"operation", metricEntry.operation().getValue());
new HashMap<String, Object>(
Map.of(
"name", sanitizeString(metricEntry.name()),
"value", sanitizeNumber(metricEntry.value()),
"operation", metricEntry.operation().getValue()));
if (app != null) {
metricData.put("app", app);
}
return OBJECT_MAPPER.writeValueAsString(metricData);
} catch (JsonProcessingException e) {
return createFallbackMetricJson(metricEntry, e);
Expand Down
3 changes: 3 additions & 0 deletions src/test/java/io/logdash/sdk/LogdashTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ void should_configure_all_builder_options() {
.requestTimeoutMs(20000L)
.shutdownTimeoutMs(15000L)
.maxConcurrentRequests(50)
.app("desktop")
.build()) {

// Assert
Expand All @@ -118,6 +119,7 @@ void should_configure_all_builder_options() {
assertThat(config.requestTimeoutMs()).isEqualTo(20000L);
assertThat(config.shutdownTimeoutMs()).isEqualTo(15000L);
assertThat(config.maxConcurrentRequests()).isEqualTo(50);
assertThat(config.app()).isEqualTo("desktop");
}
}

Expand Down Expand Up @@ -163,6 +165,7 @@ void should_handle_builder_with_all_default_values() {
assertThat(config.requestTimeoutMs()).isEqualTo(15000L);
assertThat(config.shutdownTimeoutMs()).isEqualTo(10000L);
assertThat(config.maxConcurrentRequests()).isEqualTo(20);
assertThat(config.app()).isNull();
}
}

Expand Down
29 changes: 28 additions & 1 deletion src/test/java/io/logdash/sdk/config/LogdashConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import io.logdash.sdk.exception.LogdashException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;

import static org.assertj.core.api.Assertions.*;

Expand All @@ -21,6 +24,7 @@ void should_create_config_with_valid_parameters() {
.requestTimeoutMs(15000L)
.shutdownTimeoutMs(10000L)
.maxConcurrentRequests(10)
.app("desktop")
.build();

// Assert
Expand All @@ -33,6 +37,7 @@ void should_create_config_with_valid_parameters() {
assertThat(config.requestTimeoutMs()).isEqualTo(15000L);
assertThat(config.shutdownTimeoutMs()).isEqualTo(10000L);
assertThat(config.maxConcurrentRequests()).isEqualTo(10);
assertThat(config.app()).isEqualTo("desktop");
}

@Test
Expand All @@ -50,6 +55,27 @@ void should_use_default_values() {
assertThat(config.requestTimeoutMs()).isEqualTo(15000L);
assertThat(config.shutdownTimeoutMs()).isEqualTo(10000L);
assertThat(config.maxConcurrentRequests()).isEqualTo(10);
assertThat(config.app()).isNull();
}

@Test
void should_trim_app() {
// Act
var config = LogdashConfig.builder().app(" desktop ").build();

// Assert
assertThat(config.app()).isEqualTo("desktop");
}

@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {" ", "\t", "\n"})
void should_treat_blank_app_as_unset(String app) {
// Act
var config = LogdashConfig.builder().app(app).build();

// Assert
assertThat(config.app()).isNull();
}

@Test
Expand Down Expand Up @@ -178,7 +204,8 @@ void should_validate_all_parameters_in_constructor() {
1000L,
15000L,
10000L,
10
10,
null
)
).isInstanceOf(LogdashException.class)
.hasMessageContaining("Base URL cannot be null or blank");
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/io/logdash/sdk/log/LogdashLoggerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class LogdashLoggerTest {
void setUp() {
config =
new LogdashConfig(
"test-key", "https://api.logdash.io", false, false, 3, 1000L, 5000L, 10000L, 100);
"test-key", "https://api.logdash.io", false, false, 3, 1000L, 5000L, 10000L, 100, null);

given(transport.sendLog(any())).willReturn(CompletableFuture.completedFuture(null));
logger = new LogdashLogger(config, transport);
Expand Down Expand Up @@ -141,7 +141,7 @@ void should_handle_console_output_when_enabled() {
// Arrange
var configWithConsole =
new LogdashConfig(
"test-key", "https://api.logdash.io", true, false, 3, 1000L, 5000L, 10000L, 100);
"test-key", "https://api.logdash.io", true, false, 3, 1000L, 5000L, 10000L, 100, null);
var loggerWithConsole = new LogdashLogger(configWithConsole, transport);

// Act & Assert
Expand Down
3 changes: 2 additions & 1 deletion src/test/java/io/logdash/sdk/metrics/LogdashMetricsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ void setUp() {
1000L,
5000L,
10000L,
100
100,
null
);

given(transport.sendMetric(any())).willReturn(CompletableFuture.completedFuture(null));
Expand Down
Loading
Loading