|
| 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. |
0 commit comments