Skip to content

Conversation

@mergify
Copy link
Contributor

@mergify mergify bot commented Dec 2, 2025

Release notes

[rn:skip]

What does this PR do?

Managing a Go toolchain for persisting ENV vars in logstash container artifacts has become cumbersome. We already manage a java runtime so this commit presents a path forward to use that instead of Go. The Go binary is faster than java (in my testing Go would complete in around less than 200ms while java takes over 300ms). Given the container startup time is on the order of magnitute of seconds this change should be inperceptable to consumers. The benefit from consolidating in Java is worth the slightly lower performance.

Why is it important/What is the impact to the user?

This should not be noticeable, though technically starting logstash in a container will take about 200ms longer.

Checklist

  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have made corresponding change to the default configuration files (and/or docker env variables)
  • I have added tests that prove my fix is effective or that my feature works

How to test this PR locally

Build container:

➜  logstash git:(rewrite-env2yaml-in-java) ✗ ARCH="aarch64" rake artifact:docker
➜  logstash git:(rewrite-env2yaml-in-java) ✗ docker image ls
REPOSITORY                                 TAG              IMAGE ID       CREATED              SIZE
docker.elastic.co/logstash/logstash-full   9.3.0-SNAPSHOT   19652eb23245   About a minute ago   1.48GB

Run env2yaml directly or check at startup.

#!/bin/bash

JAVA_IMAGE="19652eb23245"
GO_IMAGE="docker.elastic.co/logstash/logstash:9.2.0"

echo "=== env2yaml comparison ==="

echo "Go:"
docker run --rm -e PIPELINE_WORKERS=4 --entrypoint="" $GO_IMAGE \
  bash -c "env2yaml /usr/share/logstash/config/logstash.yml 2>&1"

echo
echo "Java:"
docker run --rm -e PIPELINE_WORKERS=4 --entrypoint="" $JAVA_IMAGE \
  bash -c "env2yaml /usr/share/logstash/config/logstash.yml 2>&1"

echo
echo "=== Logstash startup comparison ==="

echo "Go startup:"
time timeout 10 docker run -e PIPELINE_WORKERS=4 --rm $GO_IMAGE

echo
echo "Java startup:"
time timeout 10 docker run -e PIPELINE_WORKERS=4 --rm $JAVA_IMAGE

Example:

=== env2yaml comparison ===
Go:
2025/11/11 23:52:04 Setting 'pipeline.workers' from environment.

Java:
2025/11/11 23:52:04 Setting 'pipeline.workers' from environment.

=== Logstash startup comparison ===
Go startup:
2025/11/11 23:52:04 Setting 'pipeline.workers' from environment.
Using bundled JDK: /usr/share/logstash/jdk
Sending Logstash logs to /usr/share/logstash/logs which is now configured via log4j2.properties
[2025-11-11T23:52:08,200][INFO ][logstash.runner          ] Log4j configuration path used is: /usr/share/logstash/config/log4j2.properties
[2025-11-11T23:52:08,202][INFO ][logstash.runner          ] Starting Logstash {"logstash.version"=>"9.2.0", "jruby.version"=>"jruby 9.4.13.0 (3.1.4) 2025-06-10 9938a3461f OpenJDK 64-Bit Server VM 21.0.8+9-LTS on 21.0.8+9-LTS +indy +jit [aarch64-linux]"}
[2025-11-11T23:52:08,203][INFO ][logstash.runner          ] JVM bootstrap flags: [-Xms1g, -Xmx1g, -Djava.awt.headless=true, -Dfile.encoding=UTF-8, -Djruby.compile.invokedynamic=true, -XX:+HeapDumpOnOutOfMemoryError, -Djava.security.egd=file:/dev/urandom, -Dlog4j2.isThreadContextMapInheritable=true, -Dls.cgroup.cpuacct.path.override=/, -Dls.cgroup.cpu.path.override=/, -Djruby.regexp.interruptible=true, -Djdk.io.File.enableADS=true, --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED, --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED, --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED, --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED, --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED, --add-opens=java.base/java.security=ALL-UNNAMED, --add-opens=java.base/java.io=ALL-UNNAMED, --add-opens=java.base/java.nio.channels=ALL-UNNAMED, --add-opens=java.base/sun.nio.ch=ALL-UNNAMED, --add-opens=java.management/sun.management=ALL-UNNAMED, -Dio.netty.allocator.maxOrder=11]
[2025-11-11T23:52:08,217][INFO ][org.logstash.jackson.StreamReadConstraintsUtil] Jackson default value override `logstash.jackson.stream-read-constraints.max-string-length` configured to `200000000` (logstash default)
[2025-11-11T23:52:08,217][INFO ][org.logstash.jackson.StreamReadConstraintsUtil] Jackson default value override `logstash.jackson.stream-read-constraints.max-number-length` configured to `10000` (logstash default)
[2025-11-11T23:52:08,217][INFO ][org.logstash.jackson.StreamReadConstraintsUtil] Jackson default value override `logstash.jackson.stream-read-constraints.max-nesting-depth` configured to `1000` (logstash default)
[2025-11-11T23:52:08,220][INFO ][logstash.settings        ] Creating directory {:setting=>"path.queue", :path=>"/usr/share/logstash/data/queue"}
[2025-11-11T23:52:08,222][INFO ][logstash.settings        ] Creating directory {:setting=>"path.dead_letter_queue", :path=>"/usr/share/logstash/data/dead_letter_queue"}
[2025-11-11T23:52:08,286][INFO ][logstash.agent           ] No persistent UUID file found. Generating new UUID {:uuid=>"dc147b65-1477-40b7-a2db-214791cc5319", :path=>"/usr/share/logstash/data/uuid"}
[2025-11-11T23:52:08,453][INFO ][logstash.agent           ] Successfully started Logstash API endpoint {:port=>9600, :ssl_enabled=>false}
[2025-11-11T23:52:08,513][INFO ][org.reflections.Reflections] Reflections took 37 ms to scan 1 urls, producing 162 keys and 557 values
[2025-11-11T23:52:08,610][INFO ][logstash.javapipeline    ][main] Pipeline `main` is configured with `pipeline.ecs_compatibility: v8` setting. All plugins in this pipeline will default to `ecs_compatibility => v8` unless explicitly configured otherwise.
[2025-11-11T23:52:08,620][INFO ][logstash.javapipeline    ][main] Starting pipeline {:pipeline_id=>"main", "pipeline.workers"=>4, "pipeline.batch.size"=>125, "pipeline.batch.delay"=>50, "pipeline.max_inflight"=>500, "batch_metric_sampling"=>"minimal", "pipeline.sources"=>["/usr/share/logstash/pipeline/logstash.conf"], :thread=>"#<Thread:0x55ea7f5c /usr/share/logstash/logstash-core/lib/logstash/java_pipeline.rb:147 run>"}
[2025-11-11T23:52:08,828][INFO ][logstash.javapipeline    ][main] Pipeline Java execution initialization time {"seconds"=>0.21}
[2025-11-11T23:52:08,829][INFO ][logstash.inputs.beats    ][main] Starting input listener {:address=>"0.0.0.0:5044"}
[2025-11-11T23:52:08,831][INFO ][logstash.javapipeline    ][main] Pipeline started {"pipeline.id"=>"main"}
[2025-11-11T23:52:08,857][INFO ][logstash.agent           ] Pipelines running {:count=>1, :running_pipelines=>[:main], :non_running_pipelines=>[]}
[2025-11-11T23:52:08,868][INFO ][org.logstash.beats.Server][main][0710cad67e8f47667bc7612580d5b91f691dd8262a4187d9eca8cf87229d04aa] Starting server on port: 5044
[2025-11-11T23:52:14,841][WARN ][logstash.runner          ] SIGTERM received. Shutting down.
[2025-11-11T23:52:14,849][WARN ][logstash.runner          ] SIGTERM received. Shutting down.
[2025-11-11T23:52:23,184][INFO ][logstash.javapipeline    ][main] Pipeline terminated {"pipeline.id"=>"main"}
[2025-11-11T23:52:24,102][INFO ][logstash.pipelinesregistry] Removed pipeline from registry successfully {:pipeline_id=>:main}
[2025-11-11T23:52:24,107][INFO ][logstash.runner          ] Logstash shut down.

real	0m19.434s
user	0m0.021s
sys	0m0.022s

Java startup:
2025/11/11 23:52:24 Setting 'pipeline.workers' from environment.
Using bundled JDK: /usr/share/logstash/jdk
Sending Logstash logs to /usr/share/logstash/logs which is now configured via log4j2.properties
[2025-11-11T23:52:27,737][INFO ][logstash.runner          ] Log4j configuration path used is: /usr/share/logstash/config/log4j2.properties
[2025-11-11T23:52:27,740][INFO ][logstash.runner          ] Starting Logstash {"logstash.version"=>"9.3.0", "jruby.version"=>"jruby 9.4.13.0 (3.1.4) 2025-06-10 9938a3461f OpenJDK 64-Bit Server VM 21.0.9+10-LTS on 21.0.9+10-LTS +indy +jit [aarch64-linux]"}
[2025-11-11T23:52:27,741][INFO ][logstash.runner          ] JVM bootstrap flags: [-Xms1g, -Xmx1g, -Djava.awt.headless=true, -Dfile.encoding=UTF-8, -XX:+HeapDumpOnOutOfMemoryError, -Djava.security.egd=file:/dev/urandom, -Dls.cgroup.cpuacct.path.override=/, -Dls.cgroup.cpu.path.override=/, -Djruby.regexp.interruptible=true, -Djruby.compile.invokedynamic=true, -Djdk.io.File.enableADS=true, -Dlog4j2.isThreadContextMapInheritable=true, --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED, --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED, --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED, --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED, --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED, --add-opens=java.base/java.security=ALL-UNNAMED, --add-opens=java.base/java.io=ALL-UNNAMED, --add-opens=java.base/java.nio.channels=ALL-UNNAMED, --add-opens=java.base/sun.nio.ch=ALL-UNNAMED, --add-opens=java.management/sun.management=ALL-UNNAMED, -Dio.netty.allocator.maxOrder=11]
[2025-11-11T23:52:27,755][INFO ][org.logstash.jackson.StreamReadConstraintsUtil] Jackson default value override `logstash.jackson.stream-read-constraints.max-string-length` configured to `200000000` (logstash default)
[2025-11-11T23:52:27,755][INFO ][org.logstash.jackson.StreamReadConstraintsUtil] Jackson default value override `logstash.jackson.stream-read-constraints.max-number-length` configured to `10000` (logstash default)
[2025-11-11T23:52:27,755][INFO ][org.logstash.jackson.StreamReadConstraintsUtil] Jackson default value override `logstash.jackson.stream-read-constraints.max-nesting-depth` configured to `1000` (logstash default)
[2025-11-11T23:52:27,758][INFO ][logstash.settings        ] Creating directory {:setting=>"path.queue", :path=>"/usr/share/logstash/data/queue"}
[2025-11-11T23:52:27,760][INFO ][logstash.settings        ] Creating directory {:setting=>"path.dead_letter_queue", :path=>"/usr/share/logstash/data/dead_letter_queue"}
[2025-11-11T23:52:27,822][INFO ][logstash.agent           ] No persistent UUID file found. Generating new UUID {:uuid=>"25df194b-1282-49b1-945f-26f145f99d1e", :path=>"/usr/share/logstash/data/uuid"}
[2025-11-11T23:52:27,984][INFO ][logstash.agent           ] Successfully started Logstash API endpoint {:port=>9600, :ssl_enabled=>false}
[2025-11-11T23:52:28,045][INFO ][org.reflections.Reflections] Reflections took 39 ms to scan 1 urls, producing 162 keys and 558 values
[2025-11-11T23:52:28,153][INFO ][logstash.javapipeline    ][main] Pipeline `main` is configured with `pipeline.ecs_compatibility: v8` setting. All plugins in this pipeline will default to `ecs_compatibility => v8` unless explicitly configured otherwise.
[2025-11-11T23:52:28,164][INFO ][logstash.javapipeline    ][main] Starting pipeline {:pipeline_id=>"main", "pipeline.workers"=>4, "pipeline.batch.size"=>125, "pipeline.batch.delay"=>50, "pipeline.max_inflight"=>500, "batch_metric_sampling"=>"minimal", "pipeline.sources"=>["/usr/share/logstash/pipeline/logstash.conf"], :thread=>"#<Thread:0x775975c0 /usr/share/logstash/logstash-core/lib/logstash/java_pipeline.rb:147 run>"}
[2025-11-11T23:52:28,351][INFO ][logstash.javapipeline    ][main] Pipeline Java execution initialization time {"seconds"=>0.19}
[2025-11-11T23:52:28,352][INFO ][logstash.inputs.beats    ][main] Starting input listener {:address=>"0.0.0.0:5044"}
[2025-11-11T23:52:28,354][INFO ][logstash.javapipeline    ][main] Pipeline started {"pipeline.id"=>"main"}
[2025-11-11T23:52:28,359][INFO ][logstash.agent           ] Pipelines running {:count=>1, :running_pipelines=>[:main], :non_running_pipelines=>[]}
[2025-11-11T23:52:28,376][INFO ][org.logstash.beats.Server][main][0710cad67e8f47667bc7612580d5b91f691dd8262a4187d9eca8cf87229d04aa] Starting server on port: 5044
[2025-11-11T23:52:34,268][WARN ][logstash.runner          ] SIGTERM received. Shutting down.
[2025-11-11T23:52:34,278][WARN ][logstash.runner          ] SIGTERM received. Shutting down.
[2025-11-11T23:52:42,608][INFO ][logstash.javapipeline    ][main] Pipeline terminated {"pipeline.id"=>"main"}
[2025-11-11T23:52:43,552][INFO ][logstash.pipelinesregistry] Removed pipeline from registry successfully {:pipeline_id=>:main}
[2025-11-11T23:52:43,558][INFO ][logstash.runner          ] Logstash shut down.

real	0m19.441s
user	0m0.019s
sys	0m0.019s

This is an automatic backport of pull request #18423 done by [Mergify](https://mergify.com).

* WIP: Rewrite Env2yaml in java instead of Go

Managing a Go toolchain for persisting ENV vars in logstash container artifacts
has become cumbersome. We already manage a java runtime so this commit presents
a path forward to use that instead of Go. The Go binary is faster than java (in
my testing Go would complete in around less than 200ms while java takes over
300ms). Given the container startup time is on the order of magnitute of seconds
this change should be inperceptable to consumers. The benefit from consolidating
in Java is worth the slightly lower performance.

* Use TreeMap in java to try to replicate lexicographical order

* Explicit imports and TreeMap everywher

* Go removals and ironbank workflow update

* More non-code removals

* Update based on codereview

Use snakeyaml-engine and some java flags for faster execution

* Build env2yaml in stage

Build env2yaml in a separate build stage for container artifacts. Include
its dependencies and manage separately from logstash. Continue to use the
java runtime in the final container to run the program, but manage the classpath
separately. Note this did not use gradle for dependency management because installing
that as a depdendcy was not worth it compared with downloading a jar directly.

* Use gradle to manage snakeyaml-engine dependency

Use gradle (and a dedicated gradle base image) for building env2yaml

* Refactor to build env2yaml with gradle rather than in docker build

Dont rely on compiling at docker build time, rather do it when logstash
compilation is done.

* Dont try to use snakeyaml from jruby

The complexity around trying to copy over the jar shipped with jruby is not worth
how easy it is to just manage it with gradle. This helps with keeping env2yaml contained.

* Add license for snakeyaml-engine

Licence from https://bitbucket.org/snakeyaml/snakeyaml-engine/src/master/LICENSE.txt

* Cleanup and bugfix

* Stop skipping empty env vars

I mistakenly thought I had observed this behavior in the go version.

* Remove quotes from interpolated values

Even though we set `.setDefaultScalarStyle(ScalarStyle.PLAIN)` snakeyaml-engine
ends up quoting `${}` values. This commit removes them as this was not the behavior
with the go version.

(cherry picked from commit b15c6c5)

# Conflicts:
#	docker/data/logstash/env2yaml/env2yaml.go
#	docker/templates/Dockerfile.erb
#	rakelib/artifacts.rake
@mergify mergify bot added backport conflicts Detected git conflicts labels Dec 2, 2025
@mergify
Copy link
Contributor Author

mergify bot commented Dec 2, 2025

Cherry-pick of b15c6c5 has failed:

On branch mergify/bp/9.1/pr-18423
Your branch is up to date with 'origin/9.1'.

You are currently cherry-picking commit b15c6c50.
  (fix conflicts and run "git cherry-pick --continue")
  (use "git cherry-pick --skip" to skip this patch)
  (use "git cherry-pick --abort" to cancel the cherry-pick operation)

Changes to be committed:
	modified:   .gitignore
	modified:   build.gradle
	modified:   docker/Makefile
	new file:   docker/data/logstash/env2yaml/build.gradle
	new file:   docker/data/logstash/env2yaml/env2yaml
	deleted:    docker/data/logstash/env2yaml/go.mod
	deleted:    docker/data/logstash/env2yaml/go.sum
	new file:   docker/data/logstash/env2yaml/src/main/java/org/logstash/env2yaml/Env2Yaml.java
	deleted:    docker/ironbank/go/src/env2yaml/go.mod
	deleted:    docker/ironbank/go/src/env2yaml/go.sum
	deleted:    docker/ironbank/go/src/env2yaml/vendor/modules.txt
	modified:   docker/templates/IronbankDockerfile.erb
	modified:   docker/templates/hardening_manifest.yaml.erb
	modified:   settings.gradle
	modified:   tools/dependencies-report/src/main/resources/licenseMapping.csv
	new file:   tools/dependencies-report/src/main/resources/notices/org.snakeyaml!snakeyaml-engine-NOTICE.txt

Unmerged paths:
  (use "git add/rm <file>..." as appropriate to mark resolution)
	deleted by them: docker/data/logstash/env2yaml/env2yaml.go
	both modified:   docker/templates/Dockerfile.erb
	both modified:   rakelib/artifacts.rake

To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally

@mergify mergify bot mentioned this pull request Dec 2, 2025
5 tasks
@github-actions
Copy link
Contributor

github-actions bot commented Dec 2, 2025

🤖 GitHub comments

Just comment with:

  • run docs-build : Re-trigger the docs validation. (use unformatted text in the comment!)
  • /run exhaustive tests : Run the exhaustive tests Buildkite pipeline.

@elasticmachine
Copy link
Collaborator

💚 Build Succeeded

History

cc @donoghuc

Copy link
Member

@donoghuc donoghuc left a comment

Choose a reason for hiding this comment

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

Merge conflicts solved. Going to wait until I get 8.19 fully sorted (and see stability in main/9.2 until I merge this.

Copy link
Member

@donoghuc donoghuc left a comment

Choose a reason for hiding this comment

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

Comfortable with stability and 8.19 backport. Going to merge.

@donoghuc donoghuc merged commit 7bc74ba into 9.1 Dec 5, 2025
11 checks passed
@donoghuc donoghuc deleted the mergify/bp/9.1/pr-18423 branch December 5, 2025 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport conflicts Detected git conflicts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants