Skip to content

Commit 4e37fdd

Browse files
committed
initial release of temporal-parseable java plugin
0 parents  commit 4e37fdd

27 files changed

Lines changed: 2533 additions & 0 deletions

.github/workflows/ci.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["main", "master"]
6+
pull_request:
7+
branches: ["main", "master"]
8+
9+
jobs:
10+
lint:
11+
name: Lint (Checkstyle)
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Set up JDK 11
17+
uses: actions/setup-java@v4
18+
with:
19+
java-version: "11"
20+
distribution: temurin
21+
cache: maven
22+
23+
- name: Run Checkstyle
24+
run: mvn --batch-mode checkstyle:check -Dgpg.skip=true
25+
26+
type-check:
27+
name: Type Check (compile)
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v4
31+
32+
- name: Set up JDK 11
33+
uses: actions/setup-java@v4
34+
with:
35+
java-version: "11"
36+
distribution: temurin
37+
cache: maven
38+
39+
- name: Compile
40+
run: mvn --batch-mode compile -Dgpg.skip=true
41+
42+
test:
43+
name: Unit Tests
44+
runs-on: ubuntu-latest
45+
steps:
46+
- uses: actions/checkout@v4
47+
48+
- name: Set up JDK 11
49+
uses: actions/setup-java@v4
50+
with:
51+
java-version: "11"
52+
distribution: temurin
53+
cache: maven
54+
55+
- name: Run tests
56+
run: mvn --batch-mode test -Dgpg.skip=true
57+
58+
- name: Upload test reports
59+
if: always()
60+
uses: actions/upload-artifact@v4
61+
with:
62+
name: test-reports
63+
path: target/surefire-reports/

.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Maven
2+
target/
3+
*.class
4+
*.jar
5+
*.war
6+
*.ear
7+
8+
# IDE
9+
.idea/
10+
*.iml
11+
*.iws
12+
*.ipr
13+
.vscode/
14+
.settings/
15+
.classpath
16+
.project
17+
.factorypath
18+
19+
# OS
20+
.DS_Store
21+
Thumbs.db
22+
23+
# Secrets / local overrides
24+
*.env
25+
.env.*
26+
.claude

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Changelog
2+
3+
All notable changes to `temporal-parseable` (Java SDK) are documented here.
4+
5+
## [0.1.0] — 2026-06-09
6+
7+
### Added
8+
- `ParseablePlugin` — single entry point; wraps config + emitter + interceptor wiring
9+
- `ParseableConfig` — all settings configurable via `PARSEABLE_*` environment variables (feature-parity with TypeScript and Python SDKs)
10+
- `ParseableEmitter` — builds and owns `SdkTracerProvider` + `SdkLoggerProvider`; exports via OTLP/HTTP directly to Parseable
11+
- `ParseableWorkerInterceptor` — top-level `WorkerInterceptor` implementation
12+
- `ParseableWorkflowInboundInterceptor` — emits `started` / `completed` / `failed` workflow events; replay-safe via `Workflow.isReplaying()`
13+
- `ParseableWorkflowOutboundInterceptor` — chained outbound interceptor (extensible for child-workflow / signal tracking)
14+
- `ParseableActivityInboundInterceptor` — emits `started` / `completed` / `failed` activity events
15+
- `SanitizingSpanExporter` — strips/flattens non-primitive span attributes so Parseable's OTLP ingest doesn't reject them
16+
- Example `Worker` and `Client` classes under `examples/`
17+
- Unit tests: `ParseableConfigTest`, `SanitizingSpanExporterTest`
18+
- Integration tests tagged `integration`, skipped in CI by default
19+
- GitHub Actions CI: Java 11/17/21 matrix + Maven Central publish on GitHub release

INTEGRATION.md

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# Integration Guide — temporal-parseable (Java)
2+
3+
This guide walks you through installing the Parseable Temporal plugin for Java, configuring your
4+
Parseable instance, and querying the telemetry data it produces.
5+
6+
---
7+
8+
## Prerequisites
9+
10+
- Java 11 or newer
11+
- A running Parseable instance ([self-hosted](https://www.parseable.com/docs/installation) or
12+
[Parseable Cloud](https://www.parseable.com/docs/cloud))
13+
- Temporal dev server or Temporal Cloud
14+
15+
---
16+
17+
## 1. Add the dependency
18+
19+
**Maven (`pom.xml`):**
20+
21+
```xml
22+
<dependency>
23+
<groupId>com.parseable</groupId>
24+
<artifactId>temporal-parseable</artifactId>
25+
<version>0.1.0</version>
26+
</dependency>
27+
```
28+
29+
**Gradle (`build.gradle`):**
30+
31+
```groovy
32+
implementation 'com.parseable:temporal-parseable:0.1.0'
33+
```
34+
35+
---
36+
37+
## 2. Pre-create Parseable streams
38+
39+
Parseable requires streams to exist before ingestion. Create them once with:
40+
41+
```bash
42+
export PARSEABLE_ENDPOINT=https://your-parseable:8000
43+
export PARSEABLE_USERNAME=admin
44+
export PARSEABLE_PASSWORD=password
45+
46+
# Log stream
47+
curl -X PUT "$PARSEABLE_ENDPOINT/api/v1/logstream" \
48+
-H "X-P-Stream: temporal-logs" \
49+
-u "$PARSEABLE_USERNAME:$PARSEABLE_PASSWORD"
50+
51+
# Trace stream
52+
curl -X PUT "$PARSEABLE_ENDPOINT/api/v1/logstream" \
53+
-H "X-P-Stream: temporal-traces" \
54+
-u "$PARSEABLE_USERNAME:$PARSEABLE_PASSWORD"
55+
```
56+
57+
---
58+
59+
## 3. Configure environment variables
60+
61+
| Variable | Default | Description |
62+
| ----------------------------------- | --------------------------------- | -------------------------------------- |
63+
| `PARSEABLE_ENDPOINT` | `https://demo.parseable.com:8000` | Base URL of your Parseable instance |
64+
| `PARSEABLE_USERNAME` | `admin` | HTTP Basic auth username |
65+
| `PARSEABLE_PASSWORD` | `password` | HTTP Basic auth password |
66+
| `PARSEABLE_LOG_STREAM` | `temporal-logs` | Stream name for log records |
67+
| `PARSEABLE_TRACE_STREAM` | `temporal-traces` | Stream name for trace spans |
68+
| `PARSEABLE_TEMPORAL_HOST` | `localhost:7233` | Temporal server gRPC address |
69+
| `PARSEABLE_TEMPORAL_NAMESPACE` | `default` | Temporal namespace |
70+
| `PARSEABLE_SERVICE_NAME` | `temporal-worker` | OTel `service.name` resource attribute |
71+
| `PARSEABLE_BATCH_EXPORT_TIMEOUT_MS` | `5000` | Max ms to wait for a batch export |
72+
73+
---
74+
75+
## 4. Wire up the plugin
76+
77+
```java
78+
import com.parseable.temporal.ParseableConfig;
79+
import com.parseable.temporal.ParseablePlugin;
80+
import io.temporal.client.WorkflowClient;
81+
import io.temporal.client.WorkflowClientOptions;
82+
import io.temporal.serviceclient.WorkflowServiceStubs;
83+
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
84+
import io.temporal.worker.Worker;
85+
import io.temporal.worker.WorkerFactory;
86+
import io.temporal.worker.WorkerOptions;
87+
88+
ParseableConfig config = ParseableConfig.fromEnv();
89+
ParseablePlugin plugin = new ParseablePlugin(config);
90+
91+
// Register a shutdown hook so in-flight telemetry is flushed before exit
92+
Runtime.getRuntime().addShutdownHook(new Thread(plugin::close));
93+
94+
WorkflowServiceStubs stubs = WorkflowServiceStubs.newServiceStubs(
95+
plugin.configureServiceStubOptions(WorkflowServiceStubsOptions.newBuilder()).build());
96+
97+
WorkflowClient client = WorkflowClient.newInstance(stubs,
98+
plugin.configureClientOptions(WorkflowClientOptions.newBuilder()).build());
99+
100+
WorkerFactory factory = WorkerFactory.newInstance(client);
101+
102+
Worker worker = factory.newWorker(
103+
"my-task-queue",
104+
plugin.configureWorkerOptions(WorkerOptions.newBuilder()).build());
105+
106+
worker.registerWorkflowImplementationTypes(MyWorkflow.class);
107+
worker.registerActivitiesImplementations(new MyActivitiesImpl());
108+
factory.start();
109+
```
110+
111+
---
112+
113+
## 5. Log record schema
114+
115+
Every log record written to `temporal-logs` has the following fields:
116+
117+
| Field | Type | Description |
118+
| ------------------------- | ------ | ---------------------------------------------------------------------- |
119+
| `body` | string | Human-readable event description, e.g. `workflow.MyWorkflow.completed` |
120+
| `severity` | string | `INFO` (success) or `ERROR` (failure) |
121+
| `temporal.workflow.id` | string | Workflow execution ID |
122+
| `temporal.workflow.type` | string | Workflow class / type name |
123+
| `temporal.activity.type` | string | Activity type name (activity events only) |
124+
| `temporal.task_queue` | string | Task queue name |
125+
| `temporal.status` | string | `started` \| `completed` \| `failed` |
126+
| `error.message` | string | Error message (only on `failed` events) |
127+
| `temporal.plugin.version` | string | Plugin version (e.g. `0.1.0`) |
128+
| `temporal.plugin.sdk` | string | Always `java` |
129+
| `service.name` | string | OTel `service.name` resource attribute |
130+
| `temporal.namespace` | string | Temporal namespace |
131+
132+
---
133+
134+
## 6. Trace span schema
135+
136+
Every span written to `temporal-traces` mirrors the log schema but with additional OTel span
137+
metadata:
138+
139+
| Field | Type | Description |
140+
| ---------------- | ------ | ----------------------------------------------- |
141+
| `name` | string | Span name, e.g. `workflow.MyWorkflow.completed` |
142+
| `kind` | string | Always `INTERNAL` |
143+
| `status.code` | string | `OK` or `ERROR` |
144+
| `status.message` | string | Error message on `ERROR` spans |
145+
| `temporal.*` | string | Same attributes as log schema above |
146+
147+
---
148+
149+
## 7. Sample queries
150+
151+
Open the Parseable UI at `<endpoint>` and query your streams with SQL.
152+
153+
**All workflow events in the last hour:**
154+
155+
```sql
156+
SELECT p_timestamp, body, "temporal.workflow.type", "temporal.status"
157+
FROM "temporal-logs"
158+
WHERE p_timestamp > NOW() - INTERVAL '1 hour'
159+
ORDER BY p_timestamp DESC
160+
```
161+
162+
**Failed workflows:**
163+
164+
```sql
165+
SELECT p_timestamp, "temporal.workflow.id", "temporal.workflow.type", "error.message"
166+
FROM "temporal-logs"
167+
WHERE "temporal.status" = 'failed'
168+
ORDER BY p_timestamp DESC
169+
```
170+
171+
**Activity failure rate by type:**
172+
173+
```sql
174+
SELECT "temporal.activity.type",
175+
COUNT(*) AS total,
176+
SUM(CASE WHEN "temporal.status" = 'failed' THEN 1 ELSE 0 END) AS failed,
177+
ROUND(100.0 * SUM(CASE WHEN "temporal.status" = 'failed' THEN 1 ELSE 0 END) / COUNT(*), 2)
178+
AS failure_pct
179+
FROM "temporal-logs"
180+
WHERE "temporal.activity.type" IS NOT NULL
181+
GROUP BY "temporal.activity.type"
182+
ORDER BY failure_pct DESC
183+
```
184+
185+
**All workflow completions for a specific workflow type:**
186+
187+
```sql
188+
SELECT p_timestamp, "temporal.workflow.id", "temporal.status"
189+
FROM "temporal-logs"
190+
WHERE "temporal.workflow.type" = 'MyWorkflow'
191+
AND "temporal.status" = 'completed'
192+
ORDER BY p_timestamp DESC
193+
LIMIT 50
194+
```
195+
196+
---
197+
198+
## 8. Caveats
199+
200+
- **Streams must be pre-created.** Parseable does not auto-create streams on first ingest. Run
201+
the `curl -X PUT` commands in step 2 before starting your worker.
202+
203+
- **Batch export.** Records are batched by the OTel `BatchSpanProcessor` and
204+
`BatchLogRecordProcessor`. Under low traffic you may see up to a 5 s delay before events appear
205+
in Parseable. Tune `PARSEABLE_BATCH_EXPORT_TIMEOUT_MS` if needed.
206+
207+
- **Replay safety.** The plugin guards emissions with `Workflow.isReplaying()`. Do not bypass this
208+
guard if you extend the interceptors.
209+
210+
- **Non-primitive attributes.** Parseable's OTLP ingest rejects array and map-typed span
211+
attributes. `SanitizingSpanExporter` converts arrays to comma-joined strings and drops map types.
212+
This is transparent — you do not need to configure it.
213+
214+
- **Graceful shutdown.** Always call `plugin.close()` or register it as a shutdown hook. Without
215+
it, the in-memory batch buffers may not be flushed before JVM exit.

LICENSE

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Apache License
2+
Version 2.0, January 2004
3+
http://www.apache.org/licenses/
4+
5+
Copyright 2024 Parseable, Inc.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.

0 commit comments

Comments
 (0)