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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE +202 -0
- data/NOTICE.TXT +331 -0
- data/README.md +136 -0
- data/RELEASING.md +52 -0
- data/VERSION +1 -0
- data/docs/archive/README.md +3 -0
- data/docs/archive/design.md +306 -0
- data/docs/compatibility.md +12 -0
- data/docs/index.asciidoc +295 -0
- data/lib/logstash/inputs/elasticsearch_compat/batch_handler.rb +66 -0
- data/lib/logstash/inputs/elasticsearch_compat/metrics_publisher.rb +50 -0
- data/lib/logstash/inputs/elasticsearch_compat/ssl_validation.rb +64 -0
- data/lib/logstash/inputs/elasticsearch_compat.rb +149 -0
- data/lib/logstash-input-elasticsearch_compat_jars.rb +5 -0
- data/logstash-input-elasticsearch_compat.gemspec +24 -0
- data/vendor/jar-dependencies/org/logstash/plugins/inputs/logstash-input-elasticsearch_compat/0.1.0/logstash-input-elasticsearch_compat-0.1.0.jar +0 -0
- metadata +108 -0
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,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.
|