logstash-input-elasticsearch_compat 0.1.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # logstash-input-elasticsearch_compat
2
+
3
+ A Logstash input that accepts Elasticsearch Bulk API requests (`POST`/`PUT` on `/_bulk` and
4
+ `/{index}/_bulk`) and emits one Logstash event per `index` or `create` action. Point a client that
5
+ normally writes to Elasticsearch — tested with the OpenTelemetry Collector `elasticsearch` exporter
6
+ with its default settings — at Logstash instead.
7
+
8
+ This is a protocol-compatible receiver, **not** an Elasticsearch node.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ bin/logstash-plugin install logstash-input-elasticsearch_compat-<version>-java.gem
14
+ ```
15
+
16
+ ## Configuration
17
+
18
+ ```ruby
19
+ input {
20
+ elasticsearch_compat {
21
+ port => 9200
22
+ }
23
+ }
24
+ ```
25
+
26
+ | Setting | Default | Purpose |
27
+ |---|---|---|
28
+ | `host` | `"0.0.0.0"` | Listen address |
29
+ | `port` | `9200` | Listen port |
30
+ | `threads` | CPU cores | Worker threads that parse requests and push events |
31
+ | `max_pending_requests` | `200` | Requests waiting for a worker; beyond this, `429` |
32
+ | `max_content_length` | `104857600` | Maximum body size in bytes; beyond this, `413` |
33
+ | `metadata_target` | `"[@metadata][elasticsearch_compat]"` | Where routing metadata goes |
34
+ | `compression_enabled` | `true` | Compress responses when the client accepts it (requests are always decoded) |
35
+ | `ssl_*` | `ssl_enabled => false` | TLS; the same 14 settings, defaults and rules as `logstash-input-http` |
36
+
37
+ Standard input options (`id`, `tags`, `add_field`, `type`, `enable_metric`) work as usual. Full
38
+ reference: [docs/index.asciidoc](docs/index.asciidoc).
39
+
40
+ ## Routes
41
+
42
+ - `POST`/`PUT` `/_bulk` and `/{index}/_bulk` (the URL index applies only to actions without
43
+ `_index`).
44
+ - `GET /` and `HEAD /` return a minimal identity for client version detection. It reports version
45
+ `8.19.0`, a fixed compatibility placeholder that keeps the OTel exporter on its most widely
46
+ compatible metrics format.
47
+
48
+ ## Compression
49
+
50
+ Like Elasticsearch: gzip and deflate request bodies are always accepted (`max_content_length`
51
+ counts decompressed bytes); responses are compressed at level 3 whenever the client accepts it,
52
+ unless `compression_enabled => false`. Other encodings get `415`.
53
+
54
+ ## TLS
55
+
56
+ ```ruby
57
+ input {
58
+ elasticsearch_compat {
59
+ ssl_enabled => true
60
+ ssl_certificate => "/etc/logstash/certs/server.crt"
61
+ ssl_key => "/etc/logstash/certs/server.pkcs8.key" # PKCS#8
62
+ # ssl_client_authentication => "required"
63
+ # ssl_certificate_authorities => ["/etc/logstash/certs/ca.crt"]
64
+ }
65
+ }
66
+ ```
67
+
68
+ Keystores, truststores, protocols, cipher suites and handshake timeout follow
69
+ `logstash-input-http` exactly.
70
+
71
+ ## Monitoring
72
+
73
+ Counters for this input appear in `GET :9600/_node/stats/pipelines/<id>`: `bulk_requests`,
74
+ `bulk_requests_rejected_overload`/`_too_large`/`_unsupported_encoding`/`_bad_request`,
75
+ `bulk_items_failed`, `queue_submit_failures`.
76
+
77
+ ## Events
78
+
79
+ - Event fields are the source document.
80
+ - `[@metadata][elasticsearch_compat]` holds `action`, `index`, and `id`, `routing`, `pipeline`
81
+ when supplied. `@metadata` is not written by outputs; copy what downstream consumers need,
82
+ for example `mutate { copy => { "[@metadata][elasticsearch_compat][index]" => "[target_index]" } }`.
83
+ - `@timestamp`: ISO-8601 strings are parsed by Logstash. Numbers and digit strings are epoch
84
+ milliseconds, as Elasticsearch reads them (this is how the OTel exporter sends it) and keep
85
+ nanosecond precision. Anything else is tagged `_timestampparsefailure` and kept in
86
+ `_@timestamp`.
87
+
88
+ ## What a response means
89
+
90
+ - An item's `201` means the event was **accepted by the Logstash queue** — not that an output
91
+ delivered it, and not a durable checkpoint unless you use the persistent queue.
92
+ - `update` and `delete` are **not supported**: each gets an item-level `400`, never a success.
93
+ - An item without `_index` gets `400`. A document whose `@metadata` is not an object gets `400`.
94
+ - A malformed body (bad framing, JSON, or UTF-8) is rejected with `400` and **nothing** from it
95
+ is queued.
96
+ - If queuing fails mid-request, items already queued are `201`, the failing item `500`
97
+ (outcome unknown), and the rest `503`.
98
+ - **Duplicates are possible**: clients retry `429`, `500`, `503`, timeouts and disconnects, so
99
+ an event may be queued more than once. Deduplicate downstream if that matters.
100
+
101
+ Responses carry `X-Elastic-Product: Elasticsearch` because the exporter refuses servers without
102
+ it. They never contain invented Elasticsearch fields (`_version`, `_seq_no`, `_shards`, …).
103
+
104
+ ## Credentials
105
+
106
+ Credentials are **accepted and ignored**: requests with no `Authorization` header, Basic, ApiKey,
107
+ or any other scheme are all processed the same way. The header is dropped before events are
108
+ built and is never logged. Restrict access with TLS client certificates or at the network layer.
109
+
110
+ ## Not yet supported
111
+
112
+ Other Elasticsearch endpoints (`404`), Brotli/Zstd bodies (`415`), and `filter_path` expressions
113
+ beyond `took`, `errors`, `items`, `items.*`, `items.*.<field>` (`400`).
114
+
115
+ ## Client setup (OpenTelemetry Collector)
116
+
117
+ Defaults work; only the endpoint is needed:
118
+
119
+ ```yaml
120
+ exporters:
121
+ elasticsearch:
122
+ endpoint: http://logstash:9200
123
+ ```
124
+
125
+ Tested versions: [docs/compatibility.md](docs/compatibility.md).
126
+
127
+ ## Development
128
+
129
+ ```bash
130
+ mise install # Java 21, Maven, JRuby
131
+ mvn package # unit tests, shaded JAR into vendor/jar-dependencies
132
+ jruby -S rspec spec/inputs # Ruby adapter spec (after mvn package)
133
+ mvn verify -Pintegration # real Logstash + exporter (Docker)
134
+ mvn verify -Pintegration,throughput # SC-006/SC-007 performance runs (Docker)
135
+ mvn verify -Pbench # JMH (needs captured bodies in target/bench/exporter)
136
+ ```
data/RELEASING.md ADDED
@@ -0,0 +1,52 @@
1
+ # Releasing
2
+
3
+ Releases are published by GitHub Actions ([release.yml](.github/workflows/release.yml)) when a
4
+ `v<version>` tag is pushed. [ci.yml](.github/workflows/ci.yml) runs the same tests on every push to
5
+ `main` and on pull requests.
6
+
7
+ ## One-time setup
8
+
9
+ 1. Create a [RubyGems](https://rubygems.org) account with MFA enabled.
10
+ 2. On rubygems.org, add a **trusted publisher** (for a gem not yet published, use
11
+ *Pending trusted publishers* in your profile):
12
+ - Gem name: `logstash-input-elasticsearch_compat`
13
+ - Repository owner / name: `techzilla` / `logstash-input-elasticsearch_compat`
14
+ - Workflow filename: `release.yml`
15
+ - Environment: `rubygems`
16
+ 3. In the GitHub repository settings, create the `rubygems` environment. Optionally require a
17
+ reviewer so each publish waits for approval.
18
+
19
+ No API key is stored anywhere: the workflow exchanges a short-lived GitHub OIDC token for a
20
+ RubyGems credential.
21
+
22
+ ## Each release
23
+
24
+ 1. **Version**: set the same version in `VERSION` and `pom.xml` (`<version>`).
25
+ 2. **Changelog**: add a `## <version>` section to `CHANGELOG.md` (it becomes the GitHub release
26
+ notes).
27
+ 3. **Verify locally** (optional; CI repeats it), Docker required:
28
+ ```bash
29
+ mise install
30
+ mvn verify -Pintegration
31
+ jruby -S rspec spec/inputs
32
+ ```
33
+ Performance checks are not run in CI: `mvn verify -Pintegration,throughput`.
34
+ 4. **Commit, tag, push**:
35
+ ```bash
36
+ git commit -am "release: v<version>"
37
+ git tag -a v<version> -m "v<version>"
38
+ git push origin main v<version>
39
+ ```
40
+ The release workflow checks that the tag, `VERSION`, `pom.xml` and the changelog agree, runs
41
+ the full test suite, publishes the gem to RubyGems and creates a GitHub release with the gem
42
+ attached.
43
+ 5. **Smoke test** the published gem on a clean Logstash:
44
+ `bin/logstash-plugin install logstash-input-elasticsearch_compat`.
45
+
46
+ ## Manual fallback
47
+
48
+ ```bash
49
+ mvn package -DskipTests
50
+ jruby -S gem build logstash-input-elasticsearch_compat.gemspec
51
+ gem push logstash-input-elasticsearch_compat-<version>-java.gem
52
+ ```
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,3 @@
1
+ # Archive
2
+
3
+ These are here for archival purposes, they do not take precidence.
@@ -0,0 +1,306 @@
1
+ # logstash-input-elasticsearch_compat — Technical Specification
2
+
3
+ **Status:** Revised design specification
4
+ **Revision:** 0.2 — 2026-11-05
5
+ **Implementation:** Ruby Logstash input plugin with Java/Netty backend
6
+ **Initial compatibility target:** OpenTelemetry Collector Elasticsearch exporter, including Elastic EDOT Collector
7
+
8
+ ## 1. Purpose
9
+
10
+ Provide an HTTP ingestion endpoint that accepts Elasticsearch Bulk API requests and emits corresponding Logstash events. It is intended for applications and collectors already configured to send telemetry to Elasticsearch, particularly the OpenTelemetry Collector's `elasticsearch` exporter and clients built on `go-docappender`.
11
+
12
+ The plugin is a **protocol-compatible ingestion receiver**, not an Elasticsearch node. Its successful acknowledgment means that a Logstash queue submission completed normally; it does **not** certify indexing, downstream output delivery, or durable disk checkpointing.
13
+
14
+ The plugin prioritizes **permissive ingestion**: accept structurally valid documents that can be represented as Logstash events, tolerate unfamiliar Elasticsearch options, and ignore irrelevant Elasticsearch-specific semantics. Do not validate Elasticsearch mappings, data stream existence, shard state, or index privileges.
15
+
16
+ ### Non-goals
17
+
18
+ - Search/query, index management, mappings, templates, ingest pipelines, ILM, shards, or persistent document storage.
19
+ - Elasticsearch document-existence checks, version conflicts, optimistic concurrency, or true `create` uniqueness.
20
+ - Emulating a general Elasticsearch cluster or implementing arbitrary Elasticsearch client APIs.
21
+ - Exactly-once ingestion, transactional Bulk requests, or acknowledgments from downstream Logstash outputs.
22
+ - A native Logstash Java Input API implementation or Java codec plugin.
23
+
24
+ ## 2. Implementation architecture
25
+
26
+ **Decided:** implement a conventional Ruby Logstash input plugin, packaged as a Ruby gem, with nearly all functionality in a regular Java library. Do not compile against `logstash-core.jar` or depend on `co.elastic.logstash.api`.
27
+
28
+ ```text
29
+ Elasticsearch-compatible clients
30
+ |
31
+ HTTP / TLS
32
+ v
33
+ +-----------------------------------------+
34
+ | Java / Netty HTTP backend |
35
+ | - connection handling and authentication|
36
+ | - endpoint routing and request limits |
37
+ | - gzip decoding / Bulk NDJSON parsing |
38
+ | - operation metadata extraction |
39
+ | - request staging and admission bounds |
40
+ | - Bulk response encoding / filter_path |
41
+ +-------------------+---------------------+
42
+ |
43
+ bounded request/batch handoff
44
+ v
45
+ +-----------------------------------------+
46
+ | Thin Ruby Logstash input adapter |
47
+ | - configuration and lifecycle |
48
+ | - LogStash::Event creation |
49
+ | - standard event decoration |
50
+ | - queue << event |
51
+ | - submission outcomes returned to Java |
52
+ +-------------------+---------------------+
53
+ |
54
+ Logstash pipeline queue
55
+ (memory or persistent)
56
+ |
57
+ filters / outputs
58
+ ```
59
+
60
+ ### 2.1 Java responsibilities
61
+
62
+ Java owns the HTTP server, TLS, authentication checks, request routing, bounded I/O buffering, Bulk framing and parsing, gzip handling, request validation, index resolution, metadata extraction, ordered response assembly, HTTP error encoding, and low-cardinality operational counters. Java must not import Logstash-internal classes.
63
+
64
+ The Java side prepares a bounded batch of parsed records and returns an outcome-aware HTTP response after the Ruby adapter has attempted submission. Handoff occurs **per request or bounded batch**, not through a Ruby callback for every line of NDJSON.
65
+
66
+ ### 2.2 Ruby responsibilities
67
+
68
+ Ruby owns only the documented Logstash input lifecycle, configuration integration, `LogStash::Event` creation, `decorate(event)`, and insertion with `queue << event`. Ruby returns the known result of each attempted event submission to Java. Ordinary Logstash input options such as `tags`, `add_field`, `id`, and plugin metrics must remain functional.
69
+
70
+ A default codec declaration may be necessary to satisfy Logstash input conventions, but **Bulk parsing does not use the codec interface**. `logstash-codec-es_bulk` is an optional behavioral reference, not a runtime dependency.
71
+
72
+ ### 2.3 Threading and backpressure
73
+
74
+ Netty event-loop threads must not perform blocking Logstash queue writes. Use a **bounded** handoff to one or more Ruby/JRuby ingestion worker threads, with concurrency verified in integration tests. Do not assume additional worker threads improve throughput before benchmarking.
75
+
76
+ The Java backend limits simultaneous requests, body sizes, decompressed sizes, and total pending bytes. At capacity, refuse **new work before queue submission** with HTTP `429` or `503` as appropriate. A blocked Logstash `queue << event` operation has no assumed nonblocking `tryOffer` or guaranteed cancellation/timeout mechanism.
77
+
78
+ ## 3. Supported HTTP protocol
79
+
80
+ ### 3.1 Initial Bulk routes
81
+
82
+ | Method | Path | Behavior |
83
+ | --- | --- | --- |
84
+ | `POST`, `PUT` | `/_bulk` | Process Bulk NDJSON |
85
+ | `POST`, `PUT` | `/{index}/_bulk` | Use URL index when an action omits `_index` |
86
+ | `GET`, `HEAD` | `/` | Minimal identity/connectivity behavior **only where needed by tested clients** |
87
+
88
+ Do not implement node discovery, cluster health, or other Elasticsearch endpoints until a specific supported client requires them. Unknown routes return `404`; unsupported methods return `405` where appropriate.
89
+
90
+ ### 3.2 Transport and encoding
91
+
92
+ - HTTP/1.1 keep-alive, `Content-Length`, and chunked transfer encoding.
93
+ - UTF-8 Bulk NDJSON with `application/x-ndjson` and `application/json` media types.
94
+ - Support gzip request bodies when required by supported exporters; bound both compressed and expanded bytes.
95
+ - Enforce body, header, connection, request-count, and aggregate in-flight memory limits; select exact defaults through tests rather than guesswork.
96
+ - Handle request cancellation and disconnects without claiming that already-queued events were undone.
97
+
98
+ ### 3.3 Server identity
99
+
100
+ Version detection is enabled by default in the OpenTelemetry Elasticsearch exporter; the exporter can also disable it. The initial release must be tested with the pinned exporter **in its selected supported configuration**. If its default configuration requires `GET /`, implement the smallest empirically compatible identity response. Do **not** invent Lucene versions, shard counts, cluster members, or a broad menu of Elasticsearch compatibility profiles. Any `X-Elastic-Product` header or synthetic identity must be explicitly documented as compatibility behavior and tested against the target client.
101
+
102
+ ## 4. Bulk request parsing
103
+
104
+ ### 4.1 Framing
105
+
106
+ The body contains an action-and-metadata JSON line followed, for `index`, `create`, and `update`, by a source line. `delete` has no source line. A final newline is required, consistent with Elasticsearch Bulk framing. Parse the input as NDJSON, never as a single JSON array.
107
+
108
+ Validate framing, source JSON, UTF-8, and resource limits before submitting any event from that request, to avoid partially accepting a structurally malformed request. This may require a bounded staged body or a two-pass parser; **the staging mechanism is an implementation choice**. It must not buffer without bound.
109
+
110
+ ### 4.2 Action behavior
111
+
112
+ | Action | Initial behavior |
113
+ | --- | --- |
114
+ | `index` | Emit one Logstash event from the source document |
115
+ | `create` | Emit one Logstash event; do not attempt duplicate-ID detection |
116
+ | `update` | Parse framing; reject **that item** as unsupported rather than misrepresent a partial update as a full document |
117
+ | `delete` | Parse framing; reject **that item** as unsupported rather than silently discard a deletion |
118
+ | Unknown action | Reject request if its framing cannot be interpreted safely |
119
+
120
+ Recognized but unsupported actions in an otherwise structurally valid request may generate item-level errors while supported actions are admitted. Do not silently report success for discarded `update` or `delete` operations.
121
+
122
+ ### 4.3 Permissive parameter policy
123
+
124
+ Do not reject `index`/`create` merely because a request includes Elasticsearch options the receiver does not implement. In particular, tolerate and ignore the semantics of `refresh`, `wait_for_active_shards`, `require_alias`, `require_data_stream`, `timeout`, mapping hints, and ingest-pipeline selection. Accept unknown optional query and action-metadata fields unless they interfere with framing, security, resource limits, or protocol correctness.
125
+
126
+ Treat `filter_path` differently: it affects the **HTTP response** and must be honored for the patterns used by supported exporters. Unsupported response-filter expressions must not silently produce a misleading response; return a clear error or implement them as client tests require.
127
+
128
+ ### 4.4 Index and metadata
129
+
130
+ Resolve the requested target in this order: action `_index`, URL `/{index}/_bulk`, then no resolved target. For initial Elasticsearch Bulk compatibility, an operation lacking a resolvable target yields an item-level error; the plugin does not invent an index. Keep this rule isolated so a later explicitly configured fallback may be added without changing parsing.
131
+
132
+ Preserve, at minimum, the action name, resolved index, and original `_id` if present. Preserve `_routing` and `pipeline` when present as informative metadata; do **not** execute those semantics. Unknown metadata is tolerated and normally ignored. Preserve original document fields/nesting as far as the `LogStash::Event` model permits.
133
+
134
+ Place routing information in `[@metadata][elasticsearch_compat]`:
135
+
136
+ ```text
137
+ [@metadata][elasticsearch_compat][action]
138
+ [@metadata][elasticsearch_compat][index]
139
+ [@metadata][elasticsearch_compat][id] # only if supplied
140
+ [@metadata][elasticsearch_compat][routing] # optional
141
+ [@metadata][elasticsearch_compat][pipeline] # optional
142
+ ```
143
+
144
+ Do not overwrite application document fields to add routing or HTTP metadata. `@metadata` is normally excluded from downstream output serialization; pipelines forwarding to Kafka must deliberately promote any fields that Kafka consumers require. Test reserved fields and Logstash-specific coercions such as `@timestamp` rather than promising exact byte-for-byte JSON preservation.
145
+
146
+ ## 5. Acknowledgments and failure behavior
147
+
148
+ ### 5.1 Successful responses
149
+
150
+ For a structurally valid Bulk request, return Elasticsearch-shaped JSON with `took`, `errors`, and ordered `items`, unless a supported `filter_path` intentionally removes fields. Every item has its original operation name, a numeric status, and applicable `_index`/`_id` values. A normal successful queue submission may use `201` for `index` and `create` as an ingestion acknowledgment convention; it does not imply an Elasticsearch document was actually created.
151
+
152
+ Example, before response filtering:
153
+
154
+ ```json
155
+ {
156
+ "took": 6,
157
+ "errors": false,
158
+ "items": [
159
+ {"create": {"_index": "logs-app-default", "_id": "123", "status": 201}},
160
+ {"index": {"_index": "logs-app-default", "_id": "124", "status": 201}}
161
+ ]
162
+ }
163
+ ```
164
+
165
+ Never invent `_version`, `_seq_no`, `_primary_term`, shard totals, storage result, or a generated ID merely to look like Elasticsearch. If a target client requires additional fields, add only those whose meaning can be justified or clearly specified as a compatibility placeholder.
166
+
167
+ ### 5.2 Admission boundary
168
+
169
+ The Ruby adapter reports success only after `queue << event` completes normally. That establishes **submission completion**, not downstream output completion or a guaranteed durable disk checkpoint. With a persistent queue, disk durability depends on Logstash's actual persistence/checkpoint behavior and configuration.
170
+
171
+ When the input is already at its configured request-admission limit, send request-level `429` before admitting documents. In an otherwise valid request, unsupported actions can receive item-level `400` responses. If an exception occurs after some submissions, report only known outcomes and treat uncertain ones as **ambiguous**, never as guaranteed unqueued. A failed HTTP response, disconnect, or timeout may cause retries and duplicates.
172
+
173
+ Do not specify a `queue_write_timeout` or promise precise per-item `429` results until a supported, tested implementation actually exists. Never blanket-reject a partially admitted request while implying that none of its events reached Logstash.
174
+
175
+ ### 5.3 Response filtering
176
+
177
+ The OpenTelemetry Elasticsearch exporter commonly requests:
178
+
179
+ ```text
180
+ items.*._index,items.*.status,items.*.failure_store,items.*.error.type,items.*.error.reason
181
+ ```
182
+
183
+ Generate the logical result first, then apply the supported `filter_path` expressions. Test response shape with the **actual** `go-docappender`/exporter decoder, for both successes and failures. A full arbitrary Elasticsearch response-filter expression engine is not part of the initial scope.
184
+
185
+ ### 5.4 Error categories
186
+
187
+ | Situation | Initial response |
188
+ | --- | --- |
189
+ | Valid Bulk request, all accepted | HTTP `200`, `errors:false` |
190
+ | Valid Bulk request, known item failures | HTTP `200`, `errors:true`, ordered item errors |
191
+ | Malformed framing / invalid JSON | HTTP `400`, before any queue submission |
192
+ | Missing/invalid credentials | HTTP `401` or `403` |
193
+ | Too large / unsupported encoding | HTTP `413` / `415` |
194
+ | Admission capacity exhausted **before** submission | HTTP `429` (retryable) |
195
+ | Receiver shutting down/unavailable | HTTP `503` when safe to respond |
196
+ | Unexpected internal failure | HTTP `500`, with honest treatment of ambiguous submissions |
197
+
198
+ ## 6. Security and operational controls
199
+
200
+ Support HTTPS/TLS and configurable certificate/key files, HTTP Basic authentication, and static receiver-side API-key authentication using the Elasticsearch `Authorization: ApiKey ...` form. These keys identify callers to **this receiver**; they are not Elasticsearch-issued privilege grants. Support mTLS later if needed by a selected deployment.
201
+
202
+ Require bounded in-flight data, request and header limits, decompression safety, and a bounded pending-request queue. Authenticate before fully parsing large bodies where practical. Never log document bodies, passwords, API keys, or authorization headers by default. Keep operational metrics low-cardinality.
203
+
204
+ Expose the normal Logstash monitoring metrics. A small plugin readiness endpoint can be added only if deployment health checks require one; do not implement synthetic Elasticsearch cluster health for v1.
205
+
206
+ ## 7. Initial configuration surface
207
+
208
+ These are proposed setting names; defaults for throughput-sensitive limits must be established by testing.
209
+
210
+ | Setting | Purpose |
211
+ | --- | --- |
212
+ | `host`, `port` | Listener address and port |
213
+ | `ssl_enabled`, `ssl_certificate`, `ssl_key` | HTTPS configuration |
214
+ | `auth_type`, `user`, `password`, `api_keys` | Receiver-side authentication |
215
+ | `max_content_length` | Maximum compressed/uncompressed request wire body as applicable |
216
+ | `max_uncompressed_content_length` | Hard cap after decompression |
217
+ | `max_pending_requests`, `max_pending_request_bytes` | Bounded staging |
218
+ | `max_bulk_actions` | Per-request operation limit |
219
+ | `metadata_target` | Metadata field reference; default `[@metadata][elasticsearch_compat]` |
220
+
221
+ Keep optional settings out of the public interface until there is a demonstrated need. Specifically defer compatibility-profile selection, fallback indexes, synthetic health endpoints, node discovery, configurable admission timeouts, advanced authorization, and arbitrary metadata-preservation toggles.
222
+
223
+ Example **illustrative** Logstash configuration (validate setting spellings/defaults during implementation):
224
+
225
+ ```ruby
226
+ input {
227
+ elasticsearch_compat {
228
+ id => "bulk_receiver"
229
+ host => "0.0.0.0"
230
+ port => 9200
231
+ ssl_enabled => true
232
+ ssl_certificate => "/etc/logstash/certs/receiver.crt"
233
+ ssl_key => "/etc/logstash/certs/receiver.key"
234
+ auth_type => "basic"
235
+ user => "${ES_COMPAT_USER}"
236
+ password => "${ES_COMPAT_PASSWORD}"
237
+ }
238
+ }
239
+ ```
240
+
241
+ The plugin supports both Logstash memory and persistent queues through the standard input queue API. It does not implement its own durable queue.
242
+
243
+ ## 8. Build, packaging, and compatibility
244
+
245
+ - Distribute as a normal `logstash-input-elasticsearch_compat` Ruby gem containing the Ruby adapter and a precompiled Java JAR.
246
+ - Use Maven for the Java backend's dependencies, compilation, and tests; use JRuby/Bundler and gem tooling for the Ruby adapter and final gem packaging. No native Logstash Java API dependency or rebuilt Logstash core JAR is necessary.
247
+ - Keep the Java library isolated from Logstash classes; integrate through a small JRuby-facing adapter.
248
+ - Test **the same gem** against each explicitly supported Logstash version; do not claim binary/source compatibility across major versions without those tests.
249
+ - Use dependency lock/pinning and vulnerability checks for Netty and the JSON parser.
250
+ - The existing `logstash-input-http` implementation is a design reference; do not depend on its internal or undocumented classes.
251
+
252
+ ## 9. Testing and acceptance criteria
253
+
254
+ ### 9.1 Java unit tests
255
+
256
+ Cover Bulk line framing (including `delete`/`update`), invalid JSON/UTF-8, final newline rules, mixed indexes, metadata preservation, unknown parameters, gzip and decompression caps, header limits, response ordering, error serialization, and the tested `filter_path` patterns.
257
+
258
+ ### 9.2 Integration tests
259
+
260
+ Run real Logstash instances with memory and persistent queues. Test empty and saturated pipeline conditions, client disconnections, blocked queue writes, graceful shutdown, and attempts to exceed request/concurrency/memory limits. Verify that Java HTTP threads remain responsive while Logstash queue writes block.
261
+
262
+ ### 9.3 Real client compatibility
263
+
264
+ Pin at least one upstream OpenTelemetry Collector Elasticsearch exporter version and one EDOT Collector release where needed. Test the real exporter and `go-docappender` behavior for startup probing, successful Bulk ingestion, mixed supported/unsupported actions, response filtering, HTTP-level retryable overload, partial item errors, index resolution, authentication, and compression. Record the exact supported versions in a compatibility matrix.
265
+
266
+ Other Elasticsearch clients, syslog-ng, and custom integrations are **best-effort** until end-to-end tests for named versions have passed.
267
+
268
+ ### 9.4 v1 acceptance
269
+
270
+ 1. The plugin installs as a standard gem and runs on supported Logstash releases.
271
+ 2. The pinned real exporter connects and sends valid requests without response-decoding errors.
272
+ 3. `index` and `create` each produce exactly one event with correct document fields and routing metadata.
273
+ 4. Valid but irrelevant Elasticsearch parameters do not cause rejection.
274
+ 5. Unsupported `update`/`delete` actions are not falsely acknowledged as successful indexing.
275
+ 6. HTTP responses preserve operation order and work with the exporter's default `filter_path`.
276
+ 7. Accepted items are acknowledged only after normal completion of queue submission.
277
+ 8. Overload is bounded; request-level pre-admission rejection is retryable.
278
+ 9. Memory and persistent queue paths, TLS/authentication, shutdown, and client retry behavior pass integration tests.
279
+ 10. Limitations and duplication risks are documented.
280
+
281
+ ## 10. Implementation sequence
282
+
283
+ **Milestone A — working vertical slice.** Java Netty listener; `POST /_bulk`; Java parser; Ruby batch adapter; event/metadata creation; queue submission; proper Bulk success response; one pinned OTel exporter test.
284
+
285
+ **Milestone B — production receiver.** Other Bulk routes/methods; request and gzip limits; bounded admission/backpressure; TLS and authentication; supported `filter_path`; memory/PQ tests; shutdown behavior; metrics.
286
+
287
+ **Milestone C — additional demonstrated compatibility.** Add only features required by failing real-client tests (identity endpoint, request options, or further response fields). Maintain a named client/version compatibility matrix.
288
+
289
+ ## 11. Open validation items (not assumed features)
290
+
291
+ 1. **Ruby handoff:** Confirm safe JRuby worker-thread behavior and best request/batch size for queue submission.
292
+ 2. **Queue failures:** Confirm exactly what a returned versus failed `queue << event` means for each supported Logstash version/queue type; do not assume nonblocking try-write or hard timeout.
293
+ 3. **Exporter identity:** Record the actual initial version/probe requests for each pinned client; implement only required responses.
294
+ 4. **Response decoder:** Verify required item fields, ID omission behavior, `filter_path`, and retry classification against real clients.
295
+ 5. **Event conversion:** Test `@timestamp`, `@metadata`, special source fields, large/nested JSON, and binary/number precision edge cases.
296
+ 6. **Production bounds:** Derive limits and worker counts from load tests rather than embedding speculative defaults.
297
+
298
+ **Design rule:** Add API surface and implementation complexity only when required by the Bulk protocol, safe operation, or a demonstrated client behavior.
299
+
300
+ ## References
301
+
302
+ - Logstash input plugin development: https://www.elastic.co/docs/extend/logstash/input-new-plugin
303
+ - Logstash HTTP input: https://www.elastic.co/docs/reference/logstash/plugins/plugins-inputs-http
304
+ - Elasticsearch Bulk API: https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk
305
+ - OpenTelemetry Collector Elasticsearch exporter: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/exporter/elasticsearchexporter/README.md
306
+ - Elastic EDOT Collector Elasticsearch exporter: https://www.elastic.co/docs/reference/edot-collector/components/elasticsearchexporter
@@ -0,0 +1,12 @@
1
+ # Compatibility matrix
2
+
3
+ Only combinations listed here have passed end-to-end tests. Everything else is best-effort.
4
+
5
+ | Client | Client settings | Logstash | Queue | Transport | Result |
6
+ |---|---|---|---|---|---|
7
+ | OTel Collector contrib `0.165.0`, `elasticsearch` exporter (`go-docappender` v2) | Defaults (only `endpoint`: gzip and version detection on) | 9.5.6 | memory | HTTP | Pass |
8
+ | same | `version_detection` off, `compression: none`, `discover.on_start: false` | 9.5.6 | memory, persisted | HTTP | Pass |
9
+ | same | Defaults + `tls.ca_file` | 9.5.6 | memory | HTTPS | Pass |
10
+
11
+ Tested 2026-11-13 with the Testcontainers suite (`mvn verify -Pintegration`): 1,000/1,000 records
12
+ per run, 0 exporter failures, exact timestamps.