Implement IronTelemetry Java SDK
- Core client with exception/message capture - Journey and step tracking with callable support - Breadcrumb management with LinkedList ring buffer - HTTP transport using OkHttp - Full stack trace capture from throwables - Thread-safe operations with synchronized blocks - Sample rate and beforeSend filtering - Tags, extras, and user context - Async support with CompletableFuture - Builder pattern for complex objects
This commit is contained in:
parent
d72f0af6fd
commit
087bdd0bbe
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Maven
|
||||||
|
target/
|
||||||
|
pom.xml.tag
|
||||||
|
pom.xml.releaseBackup
|
||||||
|
pom.xml.versionsBackup
|
||||||
|
pom.xml.next
|
||||||
|
release.properties
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
buildNumber.properties
|
||||||
|
.mvn/timing.properties
|
||||||
|
.mvn/wrapper/maven-wrapper.jar
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
!gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
out/
|
||||||
|
bin/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Package files
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
279
README.md
279
README.md
|
|
@ -1,2 +1,277 @@
|
||||||
# irontelemetry-java
|
# IronTelemetry SDK for Java
|
||||||
IronTelemetry SDK for Java - Error monitoring and crash reporting
|
|
||||||
|
Error monitoring and crash reporting SDK for Java applications. Capture exceptions, track user journeys, and get insights to fix issues faster.
|
||||||
|
|
||||||
|
[](https://search.maven.org/artifact/com.ironservices/telemetry)
|
||||||
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Maven
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.ironservices</groupId>
|
||||||
|
<artifactId>telemetry</artifactId>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gradle
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
implementation 'com.ironservices:telemetry:0.1.0'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Basic Exception Capture
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.ironservices.telemetry.TelemetryClient;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// Initialize with your DSN
|
||||||
|
try (TelemetryClient client = new TelemetryClient("https://pk_live_xxx@irontelemetry.com")) {
|
||||||
|
try {
|
||||||
|
doSomething();
|
||||||
|
} catch (Exception e) {
|
||||||
|
client.captureException(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Journey Tracking
|
||||||
|
|
||||||
|
Track user journeys to understand the context of errors:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.ironservices.telemetry.*;
|
||||||
|
|
||||||
|
public class CheckoutService {
|
||||||
|
private final TelemetryClient client;
|
||||||
|
|
||||||
|
public CheckoutService(TelemetryClient client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processCheckout() {
|
||||||
|
Journey journey = client.startJourney("Checkout Flow");
|
||||||
|
journey.setUser("user-123", "user@example.com", "John Doe");
|
||||||
|
|
||||||
|
try {
|
||||||
|
journey.runStep("Validate Cart", BreadcrumbCategory.BUSINESS, () -> {
|
||||||
|
validateCart();
|
||||||
|
});
|
||||||
|
|
||||||
|
journey.runStep("Process Payment", BreadcrumbCategory.BUSINESS, () -> {
|
||||||
|
processPayment();
|
||||||
|
});
|
||||||
|
|
||||||
|
journey.runStep("Send Confirmation", BreadcrumbCategory.NOTIFICATION, () -> {
|
||||||
|
sendConfirmationEmail();
|
||||||
|
});
|
||||||
|
|
||||||
|
journey.complete();
|
||||||
|
} catch (Exception e) {
|
||||||
|
journey.fail(e);
|
||||||
|
client.captureException(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.ironservices.telemetry.TelemetryClient;
|
||||||
|
import com.ironservices.telemetry.TelemetryOptions;
|
||||||
|
|
||||||
|
TelemetryOptions options = new TelemetryOptions("https://pk_live_xxx@irontelemetry.com")
|
||||||
|
.setEnvironment("production")
|
||||||
|
.setAppVersion("1.2.3")
|
||||||
|
.setSampleRate(1.0) // 100% of events
|
||||||
|
.setDebug(false)
|
||||||
|
.setBeforeSend(event -> {
|
||||||
|
// Filter or modify events
|
||||||
|
if (event.getMessage() != null && event.getMessage().contains("expected")) {
|
||||||
|
return null; // Drop the event
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
|
||||||
|
TelemetryClient client = new TelemetryClient(options);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Options
|
||||||
|
|
||||||
|
| Option | Type | Default | Description |
|
||||||
|
|--------|------|---------|-------------|
|
||||||
|
| `dsn` | String | required | Your Data Source Name |
|
||||||
|
| `environment` | String | "production" | Environment name |
|
||||||
|
| `appVersion` | String | "0.0.0" | Application version |
|
||||||
|
| `sampleRate` | double | 1.0 | Sample rate (0.0 to 1.0) |
|
||||||
|
| `maxBreadcrumbs` | int | 100 | Max breadcrumbs to keep |
|
||||||
|
| `debug` | boolean | false | Enable debug logging |
|
||||||
|
| `beforeSend` | Function | null | Hook to filter/modify events |
|
||||||
|
| `enableOfflineQueue` | boolean | true | Enable offline queue |
|
||||||
|
| `maxOfflineQueueSize` | int | 500 | Max offline queue size |
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Automatic Stack Traces**: Full stack traces captured with every exception
|
||||||
|
- **Journey Tracking**: Track user flows and correlate errors with context
|
||||||
|
- **Breadcrumbs**: Leave a trail of events leading up to an error
|
||||||
|
- **User Context**: Associate errors with specific users
|
||||||
|
- **Tags & Extras**: Add custom metadata to your events
|
||||||
|
- **Async Support**: CompletableFuture support for async operations
|
||||||
|
- **Thread-Safe**: All operations are safe for concurrent use
|
||||||
|
|
||||||
|
## Breadcrumbs
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Add simple breadcrumbs
|
||||||
|
client.addBreadcrumb("User clicked checkout button", BreadcrumbCategory.UI);
|
||||||
|
client.addBreadcrumb("Payment API called", BreadcrumbCategory.HTTP);
|
||||||
|
|
||||||
|
// With level
|
||||||
|
client.addBreadcrumb("User logged in", BreadcrumbCategory.AUTH, SeverityLevel.INFO);
|
||||||
|
|
||||||
|
// With data
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("url", "/api/checkout");
|
||||||
|
data.put("statusCode", 200);
|
||||||
|
data.put("duration", 150);
|
||||||
|
client.addBreadcrumb("API request completed", BreadcrumbCategory.HTTP, SeverityLevel.INFO, data);
|
||||||
|
|
||||||
|
// Using builder
|
||||||
|
client.addBreadcrumb(Breadcrumb.builder("Payment processed", BreadcrumbCategory.BUSINESS)
|
||||||
|
.level(SeverityLevel.INFO)
|
||||||
|
.addData("amount", 99.99)
|
||||||
|
.addData("currency", "USD")
|
||||||
|
.build());
|
||||||
|
```
|
||||||
|
|
||||||
|
### Breadcrumb Categories
|
||||||
|
|
||||||
|
```java
|
||||||
|
BreadcrumbCategory.UI // User interface interactions
|
||||||
|
BreadcrumbCategory.HTTP // HTTP requests
|
||||||
|
BreadcrumbCategory.NAVIGATION // Page/route navigation
|
||||||
|
BreadcrumbCategory.CONSOLE // Console output
|
||||||
|
BreadcrumbCategory.AUTH // Authentication events
|
||||||
|
BreadcrumbCategory.BUSINESS // Business logic events
|
||||||
|
BreadcrumbCategory.NOTIFICATION // Notification events
|
||||||
|
BreadcrumbCategory.CUSTOM // Custom events
|
||||||
|
```
|
||||||
|
|
||||||
|
## Severity Levels
|
||||||
|
|
||||||
|
```java
|
||||||
|
SeverityLevel.DEBUG
|
||||||
|
SeverityLevel.INFO
|
||||||
|
SeverityLevel.WARNING
|
||||||
|
SeverityLevel.ERROR
|
||||||
|
SeverityLevel.FATAL
|
||||||
|
```
|
||||||
|
|
||||||
|
## User Context
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Simple user ID
|
||||||
|
client.setUser("user-123");
|
||||||
|
|
||||||
|
// With email
|
||||||
|
client.setUser("user-123", "user@example.com");
|
||||||
|
|
||||||
|
// Full user object
|
||||||
|
client.setUser(User.builder("user-123")
|
||||||
|
.email("user@example.com")
|
||||||
|
.name("John Doe")
|
||||||
|
.addData("plan", "premium")
|
||||||
|
.build());
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tags and Extra Data
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Set individual tags
|
||||||
|
client.setTag("release", "v1.2.3");
|
||||||
|
client.setTag("server", "prod-1");
|
||||||
|
|
||||||
|
// Set multiple tags
|
||||||
|
Map<String, String> tags = new HashMap<>();
|
||||||
|
tags.put("release", "v1.2.3");
|
||||||
|
tags.put("server", "prod-1");
|
||||||
|
client.setTags(tags);
|
||||||
|
|
||||||
|
// Set extra data
|
||||||
|
client.setExtra("request_id", "abc-123");
|
||||||
|
|
||||||
|
Map<String, Object> extras = new HashMap<>();
|
||||||
|
extras.put("request_id", "abc-123");
|
||||||
|
extras.put("user_agent", "Mozilla/5.0...");
|
||||||
|
client.setExtras(extras);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Async Operations
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Async exception capture
|
||||||
|
CompletableFuture<SendResult> future = client.captureExceptionAsync(exception);
|
||||||
|
future.thenAccept(result -> {
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
System.out.println("Event sent: " + result.getEventId());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Async message capture
|
||||||
|
client.captureMessageAsync("Something happened", SeverityLevel.WARNING)
|
||||||
|
.thenAccept(result -> System.out.println("Sent: " + result.isSuccess()));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Spring Integration Example
|
||||||
|
|
||||||
|
```java
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
private final TelemetryClient telemetryClient;
|
||||||
|
|
||||||
|
public GlobalExceptionHandler(TelemetryClient telemetryClient) {
|
||||||
|
this.telemetryClient = telemetryClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public ResponseEntity<?> handleException(Exception e, HttpServletRequest request) {
|
||||||
|
telemetryClient.addBreadcrumb(
|
||||||
|
"HTTP Request: " + request.getMethod() + " " + request.getRequestURI(),
|
||||||
|
BreadcrumbCategory.HTTP
|
||||||
|
);
|
||||||
|
telemetryClient.captureException(e);
|
||||||
|
return ResponseEntity.status(500).body("Internal Server Error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Java 11+
|
||||||
|
- OkHttp 4.x
|
||||||
|
- Gson 2.x
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- [Documentation](https://www.irontelemetry.com/docs)
|
||||||
|
- [Dashboard](https://www.irontelemetry.com)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see [LICENSE](LICENSE) for details.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.ironservices</groupId>
|
||||||
|
<artifactId>telemetry</artifactId>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>IronTelemetry</name>
|
||||||
|
<description>Error monitoring and crash reporting SDK for Java applications</description>
|
||||||
|
<url>https://github.com/IronServices/irontelemetry-java</url>
|
||||||
|
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>MIT License</name>
|
||||||
|
<url>https://opensource.org/licenses/MIT</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<name>IronServices</name>
|
||||||
|
<email>support@ironservices.com</email>
|
||||||
|
<organization>IronServices</organization>
|
||||||
|
<organizationUrl>https://www.ironservices.com</organizationUrl>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
|
||||||
|
<scm>
|
||||||
|
<connection>scm:git:git://github.com/IronServices/irontelemetry-java.git</connection>
|
||||||
|
<developerConnection>scm:git:ssh://github.com:IronServices/irontelemetry-java.git</developerConnection>
|
||||||
|
<url>https://github.com/IronServices/irontelemetry-java</url>
|
||||||
|
</scm>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<gson.version>2.10.1</gson.version>
|
||||||
|
<okhttp.version>4.12.0</okhttp.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>${gson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
<version>${okhttp.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>5.10.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>11</source>
|
||||||
|
<target>11</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<version>3.3.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-sources</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar-no-fork</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.6.3</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-javadocs</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a breadcrumb event leading up to an error.
|
||||||
|
*/
|
||||||
|
public class Breadcrumb {
|
||||||
|
private final Instant timestamp;
|
||||||
|
private final BreadcrumbCategory category;
|
||||||
|
private final String message;
|
||||||
|
private final SeverityLevel level;
|
||||||
|
private final Map<String, Object> data;
|
||||||
|
|
||||||
|
private Breadcrumb(Builder builder) {
|
||||||
|
this.timestamp = builder.timestamp != null ? builder.timestamp : Instant.now();
|
||||||
|
this.category = builder.category;
|
||||||
|
this.message = builder.message;
|
||||||
|
this.level = builder.level != null ? builder.level : SeverityLevel.INFO;
|
||||||
|
this.data = builder.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BreadcrumbCategory getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SeverityLevel getLevel() {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(String message, BreadcrumbCategory category) {
|
||||||
|
return new Builder(message, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private Instant timestamp;
|
||||||
|
private final BreadcrumbCategory category;
|
||||||
|
private final String message;
|
||||||
|
private SeverityLevel level;
|
||||||
|
private Map<String, Object> data = new HashMap<>();
|
||||||
|
|
||||||
|
public Builder(String message, BreadcrumbCategory category) {
|
||||||
|
this.message = message;
|
||||||
|
this.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder timestamp(Instant timestamp) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder level(SeverityLevel level) {
|
||||||
|
this.level = level;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder data(Map<String, Object> data) {
|
||||||
|
this.data = data;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addData(String key, Object value) {
|
||||||
|
this.data.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Breadcrumb build() {
|
||||||
|
return new Breadcrumb(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Category for breadcrumbs.
|
||||||
|
*/
|
||||||
|
public enum BreadcrumbCategory {
|
||||||
|
UI("ui"),
|
||||||
|
HTTP("http"),
|
||||||
|
NAVIGATION("navigation"),
|
||||||
|
CONSOLE("console"),
|
||||||
|
AUTH("auth"),
|
||||||
|
BUSINESS("business"),
|
||||||
|
NOTIFICATION("notification"),
|
||||||
|
CUSTOM("custom");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
BreadcrumbCategory(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages a ring buffer of breadcrumbs.
|
||||||
|
*/
|
||||||
|
public class BreadcrumbManager {
|
||||||
|
private final LinkedList<Breadcrumb> breadcrumbs;
|
||||||
|
private final int maxBreadcrumbs;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
public BreadcrumbManager(int maxBreadcrumbs) {
|
||||||
|
this.breadcrumbs = new LinkedList<>();
|
||||||
|
this.maxBreadcrumbs = maxBreadcrumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a breadcrumb to the ring buffer.
|
||||||
|
*/
|
||||||
|
public void add(Breadcrumb breadcrumb) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (breadcrumbs.size() >= maxBreadcrumbs) {
|
||||||
|
breadcrumbs.removeFirst();
|
||||||
|
}
|
||||||
|
breadcrumbs.add(breadcrumb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a simple breadcrumb with just a message and category.
|
||||||
|
*/
|
||||||
|
public void add(String message, BreadcrumbCategory category) {
|
||||||
|
add(Breadcrumb.builder(message, category).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a breadcrumb with a specific level.
|
||||||
|
*/
|
||||||
|
public void add(String message, BreadcrumbCategory category, SeverityLevel level) {
|
||||||
|
add(Breadcrumb.builder(message, category).level(level).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a breadcrumb with additional data.
|
||||||
|
*/
|
||||||
|
public void add(String message, BreadcrumbCategory category, SeverityLevel level, Map<String, Object> data) {
|
||||||
|
add(Breadcrumb.builder(message, category).level(level).data(data).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all breadcrumbs as a copy.
|
||||||
|
*/
|
||||||
|
public List<Breadcrumb> getAll() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return new ArrayList<>(breadcrumbs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all breadcrumbs.
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
synchronized (lock) {
|
||||||
|
breadcrumbs.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of breadcrumbs.
|
||||||
|
*/
|
||||||
|
public int size() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return breadcrumbs.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents exception/error information.
|
||||||
|
*/
|
||||||
|
public class ExceptionInfo {
|
||||||
|
private final String type;
|
||||||
|
private final String message;
|
||||||
|
private final List<StackFrame> stacktrace;
|
||||||
|
|
||||||
|
public ExceptionInfo(String type, String message, List<StackFrame> stacktrace) {
|
||||||
|
this.type = type;
|
||||||
|
this.message = message;
|
||||||
|
this.stacktrace = stacktrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ExceptionInfo fromThrowable(Throwable throwable) {
|
||||||
|
List<StackFrame> frames = new ArrayList<>();
|
||||||
|
for (StackTraceElement element : throwable.getStackTrace()) {
|
||||||
|
frames.add(new StackFrame(element));
|
||||||
|
}
|
||||||
|
return new ExceptionInfo(
|
||||||
|
throwable.getClass().getName(),
|
||||||
|
throwable.getMessage(),
|
||||||
|
frames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<StackFrame> getStacktrace() {
|
||||||
|
return stacktrace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an active journey.
|
||||||
|
*/
|
||||||
|
public class Journey {
|
||||||
|
private final JourneyManager manager;
|
||||||
|
private final JourneyContext context;
|
||||||
|
|
||||||
|
Journey(JourneyManager manager, JourneyContext context) {
|
||||||
|
this.manager = manager;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the user for the journey.
|
||||||
|
*/
|
||||||
|
public Journey setUser(String id, String email, String name) {
|
||||||
|
context.setMetadata("userId", id);
|
||||||
|
if (email != null) {
|
||||||
|
context.setMetadata("userEmail", email);
|
||||||
|
}
|
||||||
|
if (name != null) {
|
||||||
|
context.setMetadata("userName", name);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set metadata for the journey.
|
||||||
|
*/
|
||||||
|
public Journey setMetadata(String key, Object value) {
|
||||||
|
context.setMetadata(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new step in the journey.
|
||||||
|
*/
|
||||||
|
public Step startStep(String name, BreadcrumbCategory category) {
|
||||||
|
context.setCurrentStep(name);
|
||||||
|
|
||||||
|
TelemetryClient client = manager.getClient();
|
||||||
|
if (client != null) {
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("journeyId", context.getJourneyId());
|
||||||
|
data.put("journeyName", context.getName());
|
||||||
|
client.addBreadcrumb("Started step: " + name, category, SeverityLevel.INFO, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Step(this, name, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a function as a step.
|
||||||
|
*/
|
||||||
|
public <T> T runStep(String name, BreadcrumbCategory category, Callable<T> fn) throws Exception {
|
||||||
|
Step step = startStep(name, category);
|
||||||
|
try {
|
||||||
|
T result = fn.call();
|
||||||
|
step.complete();
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
step.fail(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a void function as a step.
|
||||||
|
*/
|
||||||
|
public void runStep(String name, BreadcrumbCategory category, Runnable fn) {
|
||||||
|
Step step = startStep(name, category);
|
||||||
|
try {
|
||||||
|
fn.run();
|
||||||
|
step.complete();
|
||||||
|
} catch (Exception e) {
|
||||||
|
step.fail(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete the journey successfully.
|
||||||
|
*/
|
||||||
|
public void complete() {
|
||||||
|
TelemetryClient client = manager.getClient();
|
||||||
|
if (client != null) {
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("journeyId", context.getJourneyId());
|
||||||
|
data.put("duration", Duration.between(context.getStartedAt(), Instant.now()).toMillis());
|
||||||
|
client.addBreadcrumb("Completed journey: " + context.getName(), BreadcrumbCategory.BUSINESS, SeverityLevel.INFO, data);
|
||||||
|
}
|
||||||
|
manager.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the journey as failed.
|
||||||
|
*/
|
||||||
|
public void fail(Throwable error) {
|
||||||
|
TelemetryClient client = manager.getClient();
|
||||||
|
if (client != null) {
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("journeyId", context.getJourneyId());
|
||||||
|
data.put("duration", Duration.between(context.getStartedAt(), Instant.now()).toMillis());
|
||||||
|
if (error != null) {
|
||||||
|
data.put("error", error.getMessage());
|
||||||
|
}
|
||||||
|
client.addBreadcrumb("Failed journey: " + context.getName(), BreadcrumbCategory.BUSINESS, SeverityLevel.ERROR, data);
|
||||||
|
}
|
||||||
|
manager.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
JourneyContext getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents journey context for tracking user flows.
|
||||||
|
*/
|
||||||
|
public class JourneyContext {
|
||||||
|
private final String journeyId;
|
||||||
|
private final String name;
|
||||||
|
private String currentStep;
|
||||||
|
private final Instant startedAt;
|
||||||
|
private final Map<String, Object> metadata;
|
||||||
|
|
||||||
|
public JourneyContext(String journeyId, String name) {
|
||||||
|
this.journeyId = journeyId;
|
||||||
|
this.name = name;
|
||||||
|
this.startedAt = Instant.now();
|
||||||
|
this.metadata = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJourneyId() {
|
||||||
|
return journeyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrentStep() {
|
||||||
|
return currentStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentStep(String currentStep) {
|
||||||
|
this.currentStep = currentStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getStartedAt() {
|
||||||
|
return startedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMetadata(String key, Object value) {
|
||||||
|
this.metadata.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages journey context.
|
||||||
|
*/
|
||||||
|
public class JourneyManager {
|
||||||
|
private static final Map<Journey, TelemetryClient> journeyClients = new WeakHashMap<>();
|
||||||
|
|
||||||
|
private final TelemetryClient client;
|
||||||
|
private volatile JourneyContext current;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
public JourneyManager(TelemetryClient client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new journey.
|
||||||
|
*/
|
||||||
|
public Journey startJourney(String name) {
|
||||||
|
synchronized (lock) {
|
||||||
|
JourneyContext context = new JourneyContext(UUID.randomUUID().toString(), name);
|
||||||
|
this.current = context;
|
||||||
|
|
||||||
|
if (client != null) {
|
||||||
|
client.addBreadcrumb("Started journey: " + name, BreadcrumbCategory.BUSINESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
Journey journey = new Journey(this, context);
|
||||||
|
journeyClients.put(journey, client);
|
||||||
|
return journey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current journey context.
|
||||||
|
*/
|
||||||
|
public JourneyContext getCurrent() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the current journey.
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
synchronized (lock) {
|
||||||
|
current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TelemetryClient getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TelemetryClient getClientForJourney(Journey journey) {
|
||||||
|
return journeyClients.get(journey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents parsed DSN components.
|
||||||
|
*/
|
||||||
|
public class ParsedDSN {
|
||||||
|
private final String publicKey;
|
||||||
|
private final String host;
|
||||||
|
private final String protocol;
|
||||||
|
private final String apiBaseUrl;
|
||||||
|
|
||||||
|
private ParsedDSN(String publicKey, String host, String protocol, String apiBaseUrl) {
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.host = host;
|
||||||
|
this.protocol = protocol;
|
||||||
|
this.apiBaseUrl = apiBaseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a DSN string.
|
||||||
|
* Format: https://pk_live_xxx@irontelemetry.com
|
||||||
|
*/
|
||||||
|
public static ParsedDSN parse(String dsn) {
|
||||||
|
if (dsn == null || dsn.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("DSN cannot be null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
URI uri = new URI(dsn);
|
||||||
|
String userInfo = uri.getUserInfo();
|
||||||
|
|
||||||
|
if (userInfo == null || !userInfo.startsWith("pk_")) {
|
||||||
|
throw new IllegalArgumentException("DSN must contain a valid public key starting with pk_");
|
||||||
|
}
|
||||||
|
|
||||||
|
String protocol = uri.getScheme();
|
||||||
|
String host = uri.getHost();
|
||||||
|
int port = uri.getPort();
|
||||||
|
|
||||||
|
String apiBaseUrl = protocol + "://" + host;
|
||||||
|
if (port > 0 && port != 80 && port != 443) {
|
||||||
|
apiBaseUrl += ":" + port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ParsedDSN(userInfo, host, protocol, apiBaseUrl);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new IllegalArgumentException("Invalid DSN format: " + dsn, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPublicKey() {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProtocol() {
|
||||||
|
return protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getApiBaseUrl() {
|
||||||
|
return apiBaseUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents platform/runtime information.
|
||||||
|
*/
|
||||||
|
public class PlatformInfo {
|
||||||
|
private final String name;
|
||||||
|
private final String version;
|
||||||
|
private final String os;
|
||||||
|
|
||||||
|
public PlatformInfo(String name, String version, String os) {
|
||||||
|
this.name = name;
|
||||||
|
this.version = version;
|
||||||
|
this.os = os;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PlatformInfo current() {
|
||||||
|
return new PlatformInfo(
|
||||||
|
"java",
|
||||||
|
System.getProperty("java.version"),
|
||||||
|
System.getProperty("os.name") + " " + System.getProperty("os.version")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOs() {
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the result of sending an event.
|
||||||
|
*/
|
||||||
|
public class SendResult {
|
||||||
|
private final boolean success;
|
||||||
|
private final String eventId;
|
||||||
|
private final String error;
|
||||||
|
private final boolean queued;
|
||||||
|
|
||||||
|
private SendResult(boolean success, String eventId, String error, boolean queued) {
|
||||||
|
this.success = success;
|
||||||
|
this.eventId = eventId;
|
||||||
|
this.error = error;
|
||||||
|
this.queued = queued;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SendResult success(String eventId) {
|
||||||
|
return new SendResult(true, eventId, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SendResult failure(String error) {
|
||||||
|
return new SendResult(false, null, error, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SendResult queued(String eventId) {
|
||||||
|
return new SendResult(true, eventId, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEventId() {
|
||||||
|
return eventId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isQueued() {
|
||||||
|
return queued;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Severity level for telemetry events.
|
||||||
|
*/
|
||||||
|
public enum SeverityLevel {
|
||||||
|
DEBUG("debug"),
|
||||||
|
INFO("info"),
|
||||||
|
WARNING("warning"),
|
||||||
|
ERROR("error"),
|
||||||
|
FATAL("fatal");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
SeverityLevel(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single frame in a stack trace.
|
||||||
|
*/
|
||||||
|
public class StackFrame {
|
||||||
|
private final String function;
|
||||||
|
private final String filename;
|
||||||
|
private final int lineno;
|
||||||
|
private final int colno;
|
||||||
|
|
||||||
|
public StackFrame(String function, String filename, int lineno, int colno) {
|
||||||
|
this.function = function;
|
||||||
|
this.filename = filename;
|
||||||
|
this.lineno = lineno;
|
||||||
|
this.colno = colno;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StackFrame(StackTraceElement element) {
|
||||||
|
this.function = element.getClassName() + "." + element.getMethodName();
|
||||||
|
this.filename = element.getFileName();
|
||||||
|
this.lineno = element.getLineNumber();
|
||||||
|
this.colno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFunction() {
|
||||||
|
return function;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFilename() {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLineno() {
|
||||||
|
return lineno;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColno() {
|
||||||
|
return colno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a step within a journey.
|
||||||
|
*/
|
||||||
|
public class Step {
|
||||||
|
private final Journey journey;
|
||||||
|
private final String name;
|
||||||
|
private final BreadcrumbCategory category;
|
||||||
|
private final Instant startedAt;
|
||||||
|
private final Map<String, Object> data;
|
||||||
|
|
||||||
|
Step(Journey journey, String name, BreadcrumbCategory category) {
|
||||||
|
this.journey = journey;
|
||||||
|
this.name = name;
|
||||||
|
this.category = category;
|
||||||
|
this.startedAt = Instant.now();
|
||||||
|
this.data = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set data for the step.
|
||||||
|
*/
|
||||||
|
public Step setData(String key, Object value) {
|
||||||
|
data.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete the step successfully.
|
||||||
|
*/
|
||||||
|
public void complete() {
|
||||||
|
TelemetryClient client = journey.getContext() != null ?
|
||||||
|
JourneyManager.getClientForJourney(journey) : null;
|
||||||
|
|
||||||
|
if (client != null) {
|
||||||
|
Map<String, Object> breadcrumbData = new HashMap<>();
|
||||||
|
breadcrumbData.put("journeyId", journey.getContext().getJourneyId());
|
||||||
|
breadcrumbData.put("journeyName", journey.getContext().getName());
|
||||||
|
breadcrumbData.put("duration", Duration.between(startedAt, Instant.now()).toMillis());
|
||||||
|
breadcrumbData.putAll(data);
|
||||||
|
client.addBreadcrumb("Completed step: " + name, category, SeverityLevel.INFO, breadcrumbData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the step as failed.
|
||||||
|
*/
|
||||||
|
public void fail(Throwable error) {
|
||||||
|
TelemetryClient client = JourneyManager.getClientForJourney(journey);
|
||||||
|
|
||||||
|
if (client != null) {
|
||||||
|
Map<String, Object> breadcrumbData = new HashMap<>();
|
||||||
|
breadcrumbData.put("journeyId", journey.getContext().getJourneyId());
|
||||||
|
breadcrumbData.put("journeyName", journey.getContext().getName());
|
||||||
|
breadcrumbData.put("duration", Duration.between(startedAt, Instant.now()).toMillis());
|
||||||
|
breadcrumbData.putAll(data);
|
||||||
|
if (error != null) {
|
||||||
|
breadcrumbData.put("error", error.getMessage());
|
||||||
|
}
|
||||||
|
client.addBreadcrumb("Failed step: " + name, category, SeverityLevel.ERROR, breadcrumbData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,262 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main IronTelemetry client.
|
||||||
|
*/
|
||||||
|
public class TelemetryClient implements AutoCloseable {
|
||||||
|
private final TelemetryOptions options;
|
||||||
|
private final ParsedDSN parsedDSN;
|
||||||
|
private final Transport transport;
|
||||||
|
private final BreadcrumbManager breadcrumbs;
|
||||||
|
private final JourneyManager journeys;
|
||||||
|
private volatile User user;
|
||||||
|
private final Map<String, String> tags;
|
||||||
|
private final Map<String, Object> extra;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new TelemetryClient with a DSN.
|
||||||
|
*/
|
||||||
|
public TelemetryClient(String dsn) {
|
||||||
|
this(new TelemetryOptions(dsn));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new TelemetryClient with options.
|
||||||
|
*/
|
||||||
|
public TelemetryClient(TelemetryOptions options) {
|
||||||
|
this.options = options;
|
||||||
|
this.parsedDSN = ParsedDSN.parse(options.getDsn());
|
||||||
|
String apiBaseUrl = options.getApiBaseUrl() != null ? options.getApiBaseUrl() : parsedDSN.getApiBaseUrl();
|
||||||
|
this.transport = new Transport(parsedDSN, apiBaseUrl, options.isDebug());
|
||||||
|
this.breadcrumbs = new BreadcrumbManager(options.getMaxBreadcrumbs());
|
||||||
|
this.journeys = new JourneyManager(this);
|
||||||
|
this.tags = new HashMap<>();
|
||||||
|
this.extra = new HashMap<>();
|
||||||
|
|
||||||
|
if (options.isDebug()) {
|
||||||
|
System.out.println("[IronTelemetry] Initialized with DSN: " + parsedDSN.getApiBaseUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capture an exception and send it to the server.
|
||||||
|
*/
|
||||||
|
public SendResult captureException(Throwable throwable) {
|
||||||
|
if (throwable == null) {
|
||||||
|
return SendResult.failure("null exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
TelemetryEvent event = createEvent(SeverityLevel.ERROR, throwable.getMessage());
|
||||||
|
event.setException(ExceptionInfo.fromThrowable(throwable));
|
||||||
|
return sendEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capture an exception asynchronously.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<SendResult> captureExceptionAsync(Throwable throwable) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> captureException(throwable));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capture a message and send it to the server.
|
||||||
|
*/
|
||||||
|
public SendResult captureMessage(String message, SeverityLevel level) {
|
||||||
|
TelemetryEvent event = createEvent(level, message);
|
||||||
|
return sendEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capture a message with INFO level.
|
||||||
|
*/
|
||||||
|
public SendResult captureMessage(String message) {
|
||||||
|
return captureMessage(message, SeverityLevel.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capture a message asynchronously.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<SendResult> captureMessageAsync(String message, SeverityLevel level) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> captureMessage(message, level));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a breadcrumb.
|
||||||
|
*/
|
||||||
|
public void addBreadcrumb(String message, BreadcrumbCategory category) {
|
||||||
|
breadcrumbs.add(message, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a breadcrumb with a level.
|
||||||
|
*/
|
||||||
|
public void addBreadcrumb(String message, BreadcrumbCategory category, SeverityLevel level) {
|
||||||
|
breadcrumbs.add(message, category, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a breadcrumb with data.
|
||||||
|
*/
|
||||||
|
public void addBreadcrumb(String message, BreadcrumbCategory category, SeverityLevel level, Map<String, Object> data) {
|
||||||
|
breadcrumbs.add(message, category, level, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a breadcrumb object.
|
||||||
|
*/
|
||||||
|
public void addBreadcrumb(Breadcrumb breadcrumb) {
|
||||||
|
breadcrumbs.add(breadcrumb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the user context.
|
||||||
|
*/
|
||||||
|
public void setUser(User user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the user context by ID.
|
||||||
|
*/
|
||||||
|
public void setUser(String id) {
|
||||||
|
this.user = User.builder(id).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the user context with ID and email.
|
||||||
|
*/
|
||||||
|
public void setUser(String id, String email) {
|
||||||
|
this.user = User.builder(id).email(email).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a tag.
|
||||||
|
*/
|
||||||
|
public void setTag(String key, String value) {
|
||||||
|
synchronized (lock) {
|
||||||
|
tags.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set multiple tags.
|
||||||
|
*/
|
||||||
|
public void setTags(Map<String, String> tags) {
|
||||||
|
synchronized (lock) {
|
||||||
|
this.tags.putAll(tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set extra data.
|
||||||
|
*/
|
||||||
|
public void setExtra(String key, Object value) {
|
||||||
|
synchronized (lock) {
|
||||||
|
extra.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set multiple extra data values.
|
||||||
|
*/
|
||||||
|
public void setExtras(Map<String, Object> extras) {
|
||||||
|
synchronized (lock) {
|
||||||
|
this.extra.putAll(extras);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new journey.
|
||||||
|
*/
|
||||||
|
public Journey startJourney(String name) {
|
||||||
|
return journeys.startJourney(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current journey context.
|
||||||
|
*/
|
||||||
|
public JourneyContext getCurrentJourney() {
|
||||||
|
return journeys.getCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all breadcrumbs.
|
||||||
|
*/
|
||||||
|
public void clearBreadcrumbs() {
|
||||||
|
breadcrumbs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush pending events.
|
||||||
|
*/
|
||||||
|
public void flush() {
|
||||||
|
// Future: Implement offline queue flushing
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the client and release resources.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
flush();
|
||||||
|
transport.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TelemetryEvent createEvent(SeverityLevel level, String message) {
|
||||||
|
TelemetryEvent event = new TelemetryEvent(
|
||||||
|
UUID.randomUUID().toString(),
|
||||||
|
level,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
|
||||||
|
event.setEnvironment(options.getEnvironment());
|
||||||
|
event.setAppVersion(options.getAppVersion());
|
||||||
|
event.setBreadcrumbs(breadcrumbs.getAll());
|
||||||
|
|
||||||
|
synchronized (lock) {
|
||||||
|
event.setTags(new HashMap<>(tags));
|
||||||
|
event.setExtra(new HashMap<>(extra));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
event.setUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
JourneyContext journey = journeys.getCurrent();
|
||||||
|
if (journey != null) {
|
||||||
|
event.setJourney(journey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendResult sendEvent(TelemetryEvent event) {
|
||||||
|
// Check sample rate
|
||||||
|
if (options.getSampleRate() < 1.0 && ThreadLocalRandom.current().nextDouble() > options.getSampleRate()) {
|
||||||
|
if (options.isDebug()) {
|
||||||
|
System.out.println("[IronTelemetry] Event sampled out: " + event.getEventId());
|
||||||
|
}
|
||||||
|
return SendResult.success(event.getEventId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply beforeSend hook
|
||||||
|
if (options.getBeforeSend() != null) {
|
||||||
|
event = options.getBeforeSend().apply(event);
|
||||||
|
if (event == null) {
|
||||||
|
if (options.isDebug()) {
|
||||||
|
System.out.println("[IronTelemetry] Event dropped by beforeSend hook");
|
||||||
|
}
|
||||||
|
return SendResult.success(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transport.send(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a telemetry event payload sent to the server.
|
||||||
|
*/
|
||||||
|
public class TelemetryEvent {
|
||||||
|
private final String eventId;
|
||||||
|
private final Instant timestamp;
|
||||||
|
private final SeverityLevel level;
|
||||||
|
private final String message;
|
||||||
|
private ExceptionInfo exception;
|
||||||
|
private User user;
|
||||||
|
private Map<String, String> tags;
|
||||||
|
private Map<String, Object> extra;
|
||||||
|
private List<Breadcrumb> breadcrumbs;
|
||||||
|
private JourneyContext journey;
|
||||||
|
private String environment;
|
||||||
|
private String appVersion;
|
||||||
|
private PlatformInfo platform;
|
||||||
|
|
||||||
|
public TelemetryEvent(String eventId, SeverityLevel level, String message) {
|
||||||
|
this.eventId = eventId;
|
||||||
|
this.timestamp = Instant.now();
|
||||||
|
this.level = level;
|
||||||
|
this.message = message;
|
||||||
|
this.tags = new HashMap<>();
|
||||||
|
this.extra = new HashMap<>();
|
||||||
|
this.breadcrumbs = new ArrayList<>();
|
||||||
|
this.platform = PlatformInfo.current();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEventId() {
|
||||||
|
return eventId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SeverityLevel getLevel() {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExceptionInfo getException() {
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setException(ExceptionInfo exception) {
|
||||||
|
this.exception = exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUser(User user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getTags() {
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTags(Map<String, String> tags) {
|
||||||
|
this.tags = tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getExtra() {
|
||||||
|
return extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExtra(Map<String, Object> extra) {
|
||||||
|
this.extra = extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Breadcrumb> getBreadcrumbs() {
|
||||||
|
return breadcrumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBreadcrumbs(List<Breadcrumb> breadcrumbs) {
|
||||||
|
this.breadcrumbs = breadcrumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JourneyContext getJourney() {
|
||||||
|
return journey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJourney(JourneyContext journey) {
|
||||||
|
this.journey = journey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEnvironment() {
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnvironment(String environment) {
|
||||||
|
this.environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppVersion() {
|
||||||
|
return appVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppVersion(String appVersion) {
|
||||||
|
this.appVersion = appVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlatformInfo getPlatform() {
|
||||||
|
return platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlatform(PlatformInfo platform) {
|
||||||
|
this.platform = platform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration options for the telemetry client.
|
||||||
|
*/
|
||||||
|
public class TelemetryOptions {
|
||||||
|
private final String dsn;
|
||||||
|
private String environment = "production";
|
||||||
|
private String appVersion = "0.0.0";
|
||||||
|
private double sampleRate = 1.0;
|
||||||
|
private int maxBreadcrumbs = 100;
|
||||||
|
private boolean debug = false;
|
||||||
|
private Function<TelemetryEvent, TelemetryEvent> beforeSend;
|
||||||
|
private boolean enableOfflineQueue = true;
|
||||||
|
private int maxOfflineQueueSize = 500;
|
||||||
|
private String apiBaseUrl;
|
||||||
|
|
||||||
|
public TelemetryOptions(String dsn) {
|
||||||
|
if (dsn == null || dsn.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("DSN is required");
|
||||||
|
}
|
||||||
|
this.dsn = dsn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDsn() {
|
||||||
|
return dsn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEnvironment() {
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TelemetryOptions setEnvironment(String environment) {
|
||||||
|
this.environment = environment;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppVersion() {
|
||||||
|
return appVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TelemetryOptions setAppVersion(String appVersion) {
|
||||||
|
this.appVersion = appVersion;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getSampleRate() {
|
||||||
|
return sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TelemetryOptions setSampleRate(double sampleRate) {
|
||||||
|
this.sampleRate = Math.max(0.0, Math.min(1.0, sampleRate));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxBreadcrumbs() {
|
||||||
|
return maxBreadcrumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TelemetryOptions setMaxBreadcrumbs(int maxBreadcrumbs) {
|
||||||
|
this.maxBreadcrumbs = maxBreadcrumbs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDebug() {
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TelemetryOptions setDebug(boolean debug) {
|
||||||
|
this.debug = debug;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Function<TelemetryEvent, TelemetryEvent> getBeforeSend() {
|
||||||
|
return beforeSend;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TelemetryOptions setBeforeSend(Function<TelemetryEvent, TelemetryEvent> beforeSend) {
|
||||||
|
this.beforeSend = beforeSend;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableOfflineQueue() {
|
||||||
|
return enableOfflineQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TelemetryOptions setEnableOfflineQueue(boolean enableOfflineQueue) {
|
||||||
|
this.enableOfflineQueue = enableOfflineQueue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxOfflineQueueSize() {
|
||||||
|
return maxOfflineQueueSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TelemetryOptions setMaxOfflineQueueSize(int maxOfflineQueueSize) {
|
||||||
|
this.maxOfflineQueueSize = maxOfflineQueueSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getApiBaseUrl() {
|
||||||
|
return apiBaseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TelemetryOptions setApiBaseUrl(String apiBaseUrl) {
|
||||||
|
this.apiBaseUrl = apiBaseUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import okhttp3.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles HTTP communication with the server.
|
||||||
|
*/
|
||||||
|
public class Transport {
|
||||||
|
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||||
|
private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_INSTANT;
|
||||||
|
|
||||||
|
private final String apiBaseUrl;
|
||||||
|
private final String publicKey;
|
||||||
|
private final boolean debug;
|
||||||
|
private final OkHttpClient client;
|
||||||
|
private final Gson gson;
|
||||||
|
|
||||||
|
public Transport(ParsedDSN parsedDSN, String apiBaseUrl, boolean debug) {
|
||||||
|
this.apiBaseUrl = apiBaseUrl != null ? apiBaseUrl : parsedDSN.getApiBaseUrl();
|
||||||
|
this.publicKey = parsedDSN.getPublicKey();
|
||||||
|
this.debug = debug;
|
||||||
|
this.client = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
this.gson = new GsonBuilder().serializeNulls().create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event synchronously.
|
||||||
|
*/
|
||||||
|
public SendResult send(TelemetryEvent event) {
|
||||||
|
String url = apiBaseUrl + "/api/v1/events";
|
||||||
|
|
||||||
|
try {
|
||||||
|
String body = gson.toJson(serializeEvent(event));
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("X-Public-Key", publicKey)
|
||||||
|
.post(RequestBody.create(body, JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
String errorBody = response.body() != null ? response.body().string() : "";
|
||||||
|
String error = "HTTP " + response.code() + ": " + errorBody;
|
||||||
|
if (debug) {
|
||||||
|
System.out.println("[IronTelemetry] Failed to send event: " + error);
|
||||||
|
}
|
||||||
|
return SendResult.failure(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
System.out.println("[IronTelemetry] Event sent successfully: " + event.getEventId());
|
||||||
|
}
|
||||||
|
return SendResult.success(event.getEventId());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (debug) {
|
||||||
|
System.out.println("[IronTelemetry] Failed to send event: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return SendResult.failure(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event asynchronously.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<SendResult> sendAsync(TelemetryEvent event) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> send(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the server is reachable.
|
||||||
|
*/
|
||||||
|
public boolean isOnline() {
|
||||||
|
String url = apiBaseUrl + "/api/v1/health";
|
||||||
|
|
||||||
|
try {
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.header("X-Public-Key", publicKey)
|
||||||
|
.get()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
return response.isSuccessful();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> serializeEvent(TelemetryEvent event) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("eventId", event.getEventId());
|
||||||
|
result.put("timestamp", formatInstant(event.getTimestamp()));
|
||||||
|
result.put("level", event.getLevel().getValue());
|
||||||
|
result.put("message", event.getMessage());
|
||||||
|
result.put("tags", event.getTags());
|
||||||
|
result.put("extra", event.getExtra());
|
||||||
|
result.put("environment", event.getEnvironment());
|
||||||
|
result.put("appVersion", event.getAppVersion());
|
||||||
|
|
||||||
|
// Breadcrumbs
|
||||||
|
List<Map<String, Object>> breadcrumbs = new ArrayList<>();
|
||||||
|
for (Breadcrumb b : event.getBreadcrumbs()) {
|
||||||
|
Map<String, Object> bc = new HashMap<>();
|
||||||
|
bc.put("timestamp", formatInstant(b.getTimestamp()));
|
||||||
|
bc.put("category", b.getCategory().getValue());
|
||||||
|
bc.put("message", b.getMessage());
|
||||||
|
bc.put("level", b.getLevel().getValue());
|
||||||
|
bc.put("data", b.getData());
|
||||||
|
breadcrumbs.add(bc);
|
||||||
|
}
|
||||||
|
result.put("breadcrumbs", breadcrumbs);
|
||||||
|
|
||||||
|
// Platform
|
||||||
|
if (event.getPlatform() != null) {
|
||||||
|
Map<String, Object> platform = new HashMap<>();
|
||||||
|
platform.put("name", event.getPlatform().getName());
|
||||||
|
platform.put("version", event.getPlatform().getVersion());
|
||||||
|
platform.put("os", event.getPlatform().getOs());
|
||||||
|
result.put("platform", platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exception
|
||||||
|
if (event.getException() != null) {
|
||||||
|
Map<String, Object> exception = new HashMap<>();
|
||||||
|
exception.put("type", event.getException().getType());
|
||||||
|
exception.put("message", event.getException().getMessage());
|
||||||
|
List<Map<String, Object>> stacktrace = new ArrayList<>();
|
||||||
|
for (StackFrame f : event.getException().getStacktrace()) {
|
||||||
|
Map<String, Object> frame = new HashMap<>();
|
||||||
|
frame.put("function", f.getFunction());
|
||||||
|
frame.put("filename", f.getFilename());
|
||||||
|
frame.put("lineno", f.getLineno());
|
||||||
|
stacktrace.add(frame);
|
||||||
|
}
|
||||||
|
exception.put("stacktrace", stacktrace);
|
||||||
|
result.put("exception", exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
// User
|
||||||
|
if (event.getUser() != null) {
|
||||||
|
Map<String, Object> user = new HashMap<>();
|
||||||
|
user.put("id", event.getUser().getId());
|
||||||
|
user.put("email", event.getUser().getEmail());
|
||||||
|
user.put("name", event.getUser().getName());
|
||||||
|
user.put("data", event.getUser().getData());
|
||||||
|
result.put("user", user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Journey
|
||||||
|
if (event.getJourney() != null) {
|
||||||
|
Map<String, Object> journey = new HashMap<>();
|
||||||
|
journey.put("journeyId", event.getJourney().getJourneyId());
|
||||||
|
journey.put("name", event.getJourney().getName());
|
||||||
|
journey.put("currentStep", event.getJourney().getCurrentStep());
|
||||||
|
journey.put("startedAt", formatInstant(event.getJourney().getStartedAt()));
|
||||||
|
journey.put("metadata", event.getJourney().getMetadata());
|
||||||
|
result.put("journey", journey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatInstant(Instant instant) {
|
||||||
|
return ISO_FORMATTER.format(instant);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
client.dispatcher().executorService().shutdown();
|
||||||
|
client.connectionPool().evictAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package com.ironservices.telemetry;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents user information for context.
|
||||||
|
*/
|
||||||
|
public class User {
|
||||||
|
private final String id;
|
||||||
|
private final String email;
|
||||||
|
private final String name;
|
||||||
|
private final Map<String, Object> data;
|
||||||
|
|
||||||
|
private User(Builder builder) {
|
||||||
|
this.id = builder.id;
|
||||||
|
this.email = builder.email;
|
||||||
|
this.name = builder.name;
|
||||||
|
this.data = builder.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(String id) {
|
||||||
|
return new Builder(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private final String id;
|
||||||
|
private String email;
|
||||||
|
private String name;
|
||||||
|
private Map<String, Object> data = new HashMap<>();
|
||||||
|
|
||||||
|
public Builder(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder email(String email) {
|
||||||
|
this.email = email;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder name(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder data(Map<String, Object> data) {
|
||||||
|
this.data = data;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addData(String key, Object value) {
|
||||||
|
this.data.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User build() {
|
||||||
|
return new User(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue