Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
30 changes: 28 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ jobs:
distribution: temurin
cache: maven

- name: Compile
run: mvn --batch-mode compile -Dgpg.skip=true
- name: Compile and install library
run: mvn --batch-mode install -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true

- name: Compile examples against current library
run: mvn --batch-mode -f examples/pom.xml compile -Dgpg.skip=true

test:
name: Unit Tests
Expand All @@ -61,3 +64,26 @@ jobs:
with:
name: test-reports
path: target/surefire-reports/

integration-test:
name: Integration Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up JDK 11
uses: actions/setup-java@v4
with:
java-version: "11"
distribution: temurin
cache: maven

- name: Run integration tests
run: mvn --batch-mode test -Pintegration -Dgpg.skip=true

- name: Upload integration test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: integration-test-reports
path: target/surefire-reports/
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ Thumbs.db
# Secrets / local overrides
*.env
.env.*
.claude
.claude
AGENTS.MD
28 changes: 11 additions & 17 deletions INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ implementation 'com.parseable:temporal-parseable:0.1.0'
Parseable requires streams to exist before ingestion. Create them once with:

```bash
export PARSEABLE_ENDPOINT=https://your-parseable:8000
export PARSEABLE_USERNAME=admin
export PARSEABLE_PASSWORD=password
export PARSEABLE_ENDPOINT=https://your-parseable.example.com
export PARSEABLE_USERNAME=<your-username>
export PARSEABLE_PASSWORD=<your-password>

# Log stream
curl -X PUT "$PARSEABLE_ENDPOINT/api/v1/logstream" \
Expand All @@ -60,9 +60,9 @@ curl -X PUT "$PARSEABLE_ENDPOINT/api/v1/logstream" \

| Variable | Default | Description |
| ----------------------------------- | --------------------------------- | -------------------------------------- |
| `PARSEABLE_ENDPOINT` | `https://demo.parseable.com:8000` | Base URL of your Parseable instance |
| `PARSEABLE_USERNAME` | `admin` | HTTP Basic auth username |
| `PARSEABLE_PASSWORD` | `password` | HTTP Basic auth password |
| `PARSEABLE_ENDPOINT` | **required** | Base URL of your Parseable instance |
| `PARSEABLE_USERNAME` | **required** | HTTP Basic auth username |
| `PARSEABLE_PASSWORD` | **required** | HTTP Basic auth password |
| `PARSEABLE_LOG_STREAM` | `temporal-logs` | Stream name for log records |
| `PARSEABLE_TRACE_STREAM` | `temporal-traces` | Stream name for trace spans |
| `PARSEABLE_TEMPORAL_HOST` | `localhost:7233` | Temporal server gRPC address |
Expand All @@ -78,30 +78,24 @@ curl -X PUT "$PARSEABLE_ENDPOINT/api/v1/logstream" \
import com.parseable.temporal.ParseableConfig;
import com.parseable.temporal.ParseablePlugin;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowClientOptions;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
import io.temporal.worker.Worker;
import io.temporal.worker.WorkerFactory;
import io.temporal.worker.WorkerOptions;

ParseableConfig config = ParseableConfig.fromEnv();
ParseablePlugin plugin = new ParseablePlugin(config);
ParseablePlugin plugin = new ParseablePlugin(ParseableConfig.fromEnv());

// Register a shutdown hook so in-flight telemetry is flushed before exit
Runtime.getRuntime().addShutdownHook(new Thread(plugin::close));

WorkflowServiceStubs stubs = WorkflowServiceStubs.newServiceStubs(
plugin.configureServiceStubOptions(WorkflowServiceStubsOptions.newBuilder()).build());
WorkflowServiceStubsOptions.newBuilder().setPlugins(plugin).build());

WorkflowClient client = WorkflowClient.newInstance(stubs,
plugin.configureClientOptions(WorkflowClientOptions.newBuilder()).build());
WorkflowClient client = WorkflowClient.newInstance(stubs);

WorkerFactory factory = WorkerFactory.newInstance(client);

Worker worker = factory.newWorker(
"my-task-queue",
plugin.configureWorkerOptions(WorkerOptions.newBuilder()).build());
Worker worker = factory.newWorker("my-task-queue");

worker.registerWorkflowImplementationTypes(MyWorkflow.class);
worker.registerActivitiesImplementations(new MyActivitiesImpl());
Expand Down Expand Up @@ -138,7 +132,7 @@ metadata:

| Field | Type | Description |
| ---------------- | ------ | ----------------------------------------------- |
| `name` | string | Span name, e.g. `workflow.MyWorkflow.completed` |
| `name` | string | Span name, e.g. `workflow.MyWorkflow` or `activity.MyActivity` |
| `kind` | string | Always `INTERNAL` |
| `status.code` | string | `OK` or `ERROR` |
| `status.message` | string | Error message on `ERROR` spans |
Expand Down
47 changes: 26 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,17 @@ implementation 'com.parseable:temporal-parseable:0.1.0'

```java
// 1. Create the plugin (reads PARSEABLE_* env vars)
ParseableConfig config = ParseableConfig.fromEnv();
ParseablePlugin plugin = new ParseablePlugin(config);
ParseablePlugin plugin = new ParseablePlugin(ParseableConfig.fromEnv());

// 2. Connect to Temporal
// 2. Connect to Temporal — register the plugin once on service stubs
WorkflowServiceStubs stubs = WorkflowServiceStubs.newServiceStubs(
plugin.configureServiceStubOptions(WorkflowServiceStubsOptions.newBuilder()).build());
WorkflowServiceStubsOptions.newBuilder().setPlugins(plugin).build());

WorkflowClient client = WorkflowClient.newInstance(stubs,
plugin.configureClientOptions(WorkflowClientOptions.newBuilder()).build());
WorkflowClient client = WorkflowClient.newInstance(stubs);

// 3. Create a worker with the interceptor wired in
// 3. Create a worker factory — the plugin is propagated automatically
WorkerFactory factory = WorkerFactory.newInstance(client);
Worker worker = factory.newWorker(
"my-queue",
plugin.configureWorkerOptions(WorkerOptions.newBuilder()).build());
Worker worker = factory.newWorker("my-queue");

worker.registerWorkflowImplementationTypes(MyWorkflow.class);
worker.registerActivitiesImplementations(new MyActivitiesImpl());
Expand All @@ -61,9 +57,9 @@ All settings can be set via environment variables or the `ParseableConfig.Builde

| Environment variable | Builder method | Default |
|-----------------------------------|-----------------------------|--------------------------------------|
| `PARSEABLE_ENDPOINT` | `.endpoint(...)` | `https://demo.parseable.com:8000` |
| `PARSEABLE_USERNAME` | `.username(...)` | `admin` |
| `PARSEABLE_PASSWORD` | `.password(...)` | `password` |
| `PARSEABLE_ENDPOINT` | `.endpoint(...)` | **required** |
| `PARSEABLE_USERNAME` | `.username(...)` | **required** |
| `PARSEABLE_PASSWORD` | `.password(...)` | **required** |
| `PARSEABLE_LOG_STREAM` | `.logStream(...)` | `temporal-logs` |
| `PARSEABLE_TRACE_STREAM` | `.traceStream(...)` | `temporal-traces` |
| `PARSEABLE_TEMPORAL_HOST` | `.temporalHost(...)` | `localhost:7233` |
Expand All @@ -79,12 +75,12 @@ Parseable requires streams to exist before ingestion. Create them once:
# Log stream
curl -X PUT https://<endpoint>/api/v1/logstream \
-H "X-P-Stream: temporal-logs" \
-u admin:password
-u "$PARSEABLE_USERNAME:$PARSEABLE_PASSWORD"

# Trace stream
curl -X PUT https://<endpoint>/api/v1/logstream \
-H "X-P-Stream: temporal-traces" \
-u admin:password
-u "$PARSEABLE_USERNAME:$PARSEABLE_PASSWORD"
```

## Repository layout
Expand All @@ -111,9 +107,10 @@ examples/src/main/java/com/parseable/temporal/example/
└── Client.java # triggers example workflows

src/test/java/com/parseable/temporal/
├── ParseableConfigTest.java # unit tests for config + defaults
├── ParseableConfigTest.java # unit tests for config validation + defaults
├── SanitizingSpanExporterTest.java # unit tests for attribute sanitization
└── ParseableInterceptorTest.java # integration tests (tag: integration)
├── ParseablePluginTest.java # unit tests for plugin configuration
└── ParseableInterceptorTest.java # in-process Temporal interceptor tests
```

## Architecture
Expand Down Expand Up @@ -179,11 +176,12 @@ exits. This force-flushes both the tracer and logger providers so in-flight batc

| Test class | Type | What it covers |
|---|---|---|
| `ParseableConfigTest` | Unit | Env-var defaults, builder overrides, endpoint derivation |
| `ParseableConfigTest` | Unit | Required fields, builder overrides, endpoint derivation |
| `ParseablePluginTest` | Unit | Service/client/factory configuration, duplicate interceptor guard |
| `SanitizingSpanExporterTest` | Unit | Primitive pass-through, array flattening, flush/shutdown delegation |
| `ParseableInterceptorTest` | Integration | Workflow + activity event emission, replay-safety assertion |

Run unit tests (default):
Run tests:
```bash
mvn test
```
Expand All @@ -193,6 +191,12 @@ Run all tests including integration:
mvn test -Pintegration
```

Compile examples against the current checkout:
```bash
mvn install -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true
mvn -f examples/pom.xml compile
```

## Running the demo

```bash
Expand All @@ -201,10 +205,11 @@ temporal server start-dev

# Terminal 2 — start Parseable (or point at staging.parseable.com)
# Pre-create streams (see above), then:
mvn compile exec:java -Dexec.mainClass=com.parseable.temporal.example.Worker
mvn install -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true
mvn -f examples/pom.xml compile exec:java -Dexec.mainClass=com.parseable.temporal.example.Worker

# Terminal 3
mvn compile exec:java -Dexec.mainClass=com.parseable.temporal.example.Client
mvn -f examples/pom.xml compile exec:java -Dexec.mainClass=com.parseable.temporal.example.Client
```

## Publishing to Maven Central
Expand Down
87 changes: 87 additions & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?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.parseable</groupId>
<artifactId>temporal-parseable-examples</artifactId>
<version>0.1.0</version>
<packaging>jar</packaging>

<name>Temporal Parseable Plugin — Examples</name>
<description>Runnable demo worker and client showing how to use temporal-parseable.</description>

<properties>
<java.version>11</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<temporal.version>1.35.0</temporal.version>
<slf4j.version>2.0.16</slf4j.version>
</properties>

<dependencies>
<dependency>
<groupId>com.parseable</groupId>
<artifactId>temporal-parseable</artifactId>
<version>0.1.0</version>
</dependency>

<dependency>
<groupId>io.temporal</groupId>
<artifactId>temporal-sdk</artifactId>
<version>${temporal.version}</version>
</dependency>

<!-- Runtime logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>

<!-- Fat jar so examples run with: java -cp target/...-jar-with-dependencies.jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.7.1</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>assemble-all</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,13 @@ public final class Client {
private static final String TASK_QUEUE = "parseable-demo-queue";

public static void main(String[] args) {
ParseableConfig config = ParseableConfig.fromEnv();
ParseablePlugin plugin = new ParseablePlugin(config);
ParseablePlugin plugin = new ParseablePlugin(ParseableConfig.fromEnv());

try {
WorkflowServiceStubs stubs = WorkflowServiceStubs.newServiceStubs(
plugin.configureServiceStubOptions(
WorkflowServiceStubsOptions.newBuilder())
.build());
WorkflowServiceStubsOptions.newBuilder().setPlugins(plugin).build());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you set an endpoint and then use setPlugins(), your endpoint gets silently reset to localhost:7233. I'm not sure the plugin should even be able to set the Temporal connection in the first place.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Fixed in two ways: (1) the example now reads TEMPORAL_TARGET from env so the connection target is explicit, and (2) removed the configureServiceStubs override entirely, it was a no-op super call that gave the wrong impression the plugin touches the connection. It doesn't.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I think I'm still seeing the same type of override error happen for Temporal namespaces too: If a Temporal Cloud user sets their own namespace and then registers the plugin, it'll get silently overwritten to "default" (ParseablePlugin.java:67)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have fixed this as well


WorkflowClient client = WorkflowClient.newInstance(stubs,
plugin.configureClientOptions(
io.temporal.client.WorkflowClientOptions.newBuilder())
.build());
WorkflowClient client = WorkflowClient.newInstance(stubs);

// --- Greeting workflow (should succeed) ---
System.out.println("Starting GreetingWorkflow...");
Expand All @@ -59,6 +53,7 @@ public static void main(String[] args) {
System.out.println("Workflow failed as expected: " + e.getMessage());
}

ParseableConfig config = plugin.getConfig();
System.out.println("\nAll workflows triggered. Check Parseable streams:");
System.out.println(" Logs: " + config.logsEndpoint());
System.out.println(" Traces: " + config.tracesEndpoint());
Expand Down
Loading
Loading