Skip to content

Commit d39a056

Browse files
authored
Add logback config for zk (#37)
1 parent ac16f74 commit d39a056

File tree

13 files changed

+432
-0
lines changed

13 files changed

+432
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,7 @@ tags
8383
.history
8484
# End of https://www.gitignore.io/api/go,vim,emacs,visualstudiocode
8585
*.iml
86+
87+
88+
docker/zu/.gradle
89+
docker/zu/build

docker/bin/zookeeperStart.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ else
181181
fi
182182
cp -f /conf/log4j.properties $ZOOCFGDIR
183183
cp -f /conf/log4j-quiet.properties $ZOOCFGDIR
184+
cp -f /conf/logback.xml $ZOOCFGDIR
185+
cp -f /conf/logback-quiet.xml $ZOOCFGDIR
184186
cp -f /conf/env.sh $ZOOCFGDIR
185187

186188
if [ -f $DYNCONFIG ]; then

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,13 @@ require (
4646
github.com/google/gnostic-models v0.7.0 // indirect
4747
github.com/google/go-cmp v0.7.0 // indirect
4848
github.com/google/uuid v1.6.0 // indirect
49+
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
4950
github.com/json-iterator/go v1.1.12 // indirect
51+
github.com/moby/spdystream v0.5.0 // indirect
5052
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
5153
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
5254
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
55+
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
5356
github.com/nxadm/tail v1.4.11 // indirect
5457
github.com/pmezard/go-difflib v1.0.0 // indirect
5558
github.com/prometheus/client_golang v1.23.2 // indirect

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
22
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
3+
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
4+
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
35
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
46
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
57
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -85,6 +87,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J
8587
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
8688
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
8789
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
90+
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
91+
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
8892
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
8993
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
9094
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
@@ -98,6 +102,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
98102
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
99103
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
100104
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
105+
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
106+
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
101107
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
102108
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
103109
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -106,6 +112,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
106112
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
107113
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
108114
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
115+
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
116+
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
109117
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
110118
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
111119
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-02-23
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Design: Add Logback config so ZooKeeper logs at INFO
2+
3+
## Context
4+
5+
The operator generates a ConfigMap per ZookeeperCluster containing `zoo.cfg`, `env.sh`, `log4j.properties`, and `log4j-quiet.properties`. The ConfigMap is mounted at `/conf` and the pod’s startup script copies its contents to a writable config dir (e.g. `/data/conf`) before running `zkServer.sh --config /data/conf start-foreground`. The ZooKeeper server image uses **Logback** (logback-classic, logback-core) as the SLF4J backend; there is no Log4j on the classpath, so the existing log4j config files are never read. Logback discovers configuration by looking for `logback.xml` (or `logback.groovy` / `logback-test.xml`) on the classpath; `ZOOCFGDIR` is prepended to the classpath in `zkEnv.sh`, so a `logback.xml` placed in the config dir will be picked up when the JVM starts. The goal is to add operator-generated Logback config so that runtime logging is at INFO (or optionally ERROR for a quiet profile) instead of DEBUG.
6+
7+
## Goals / Non-Goals
8+
9+
**Goals:**
10+
11+
- Generate `logback.xml` with root and key loggers at INFO, and `logback-quiet.xml` with root and key loggers at ERROR.
12+
- Include both files in the existing ZookeeperCluster ConfigMap; no new volume or mount.
13+
- Use a CONSOLE appender with the same pattern as upstream ZooKeeper (`%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n`) for consistency.
14+
- Explicitly set logger level for `org.apache.zookeeper` and `org.eclipse.jetty` so DEBUG from those packages is suppressed regardless of any default or bundled config.
15+
16+
**Non-Goals:**
17+
18+
- No CRD field or API to select log level (INFO vs ERROR); both configs are present and the default is `logback.xml` (INFO) via Logback’s default discovery. Optional future: env or CRD to point to the quiet file via `-Dlogback.configurationFile`.
19+
- No removal of existing log4j config files (kept for any image that might use Log4j). The **startup script is updated** so that Logback configs are copied and take effect (see Decision 5).
20+
21+
## Decisions
22+
23+
### 1. Generate Logback XML in Go as raw strings
24+
25+
**Decision:** Implement two functions (e.g. `makeZkLogbackConfigString()`, `makeZkLogbackQuietConfigString()`) that return the full XML as Go raw string literals.
26+
27+
**Rationale:** The content is static and small; no user or cluster-specific substitution is required. Using a raw string avoids escaping and keeps the XML readable in code. Alternatives (embedding a file, or using text/template with a tiny template) add indirection without benefit for this size.
28+
29+
### 2. Two separate files (logback.xml and logback-quiet.xml)
30+
31+
**Decision:** Emit two files: `logback.xml` (INFO) and `logback-quiet.xml` (ERROR). Logback’s default lookup uses `logback.xml`, so the default behavior is INFO without any startup-script or env change.
32+
33+
**Rationale:** Matches the existing pattern (log4j.properties vs logback-quiet.properties). A single parameterized file would require the image to pass `-Dlogback.configurationFile` or similar, which is out of scope. Keeping the default as INFO satisfies the main use case (reduce DEBUG); the quiet file is available for future use (e.g. script or env pointing to it).
34+
35+
### 3. Keep existing log4j files unchanged
36+
37+
**Decision:** Do not remove or alter `log4j.properties` or `log4j-quiet.properties` in the ConfigMap.
38+
39+
**Rationale:** Some images or environments might still use Log4j; removing those files could break them. The change is purely additive.
40+
41+
### 4. CONSOLE appender only; explicit loggers for ZooKeeper and Jetty
42+
43+
**Decision:** Each Logback config has one CONSOLE appender with a ThresholdFilter at the chosen level, plus `<logger name="org.apache.zookeeper" level="…"/>` and `<logger name="org.eclipse.jetty" level="…"/>`, and `<root level="…">` with that appender.
44+
45+
**Rationale:** CONSOLE-only matches current operator behavior (no file appenders). Explicit loggers ensure that even if another config or a jar’s default sets DEBUG for those packages, our config takes precedence when our file is loaded first on the classpath.
46+
47+
### 5. Startup script copies Logback configs to writable config dir
48+
49+
**Decision:** The ZooKeeper startup script (e.g. `zookeeperStart.sh`) SHALL copy `logback.xml` and `logback-quiet.xml` from the mounted config source (e.g. `/conf`) to the writable config directory (e.g. `/data/conf`, i.e. `ZOOCFGDIR`) before invoking `zkServer.sh --config $ZOOCFGDIR start-foreground`, in the same way it already copies `log4j.properties`, `log4j-quiet.properties`, and `env.sh`.
50+
51+
**Rationale:** The ConfigMap is mounted read-only at `/conf`. The server runs with `ZOOCFGDIR=/data/conf` on the classpath; that directory is writable and is populated by the script. If the script does not copy the Logback files, they never appear in `/data/conf`, so Logback does not find them and falls back to default (DEBUG). Copying them ensures they are present when the JVM starts.
52+
53+
## Risks / Trade-offs
54+
55+
| Risk | Mitigation |
56+
|------|------------|
57+
| Another `logback.xml` (e.g. inside a JAR) is found before the config dir on the classpath | ZOOCFGDIR is prepended in zkEnv.sh; as long as the pod uses that script and the same config dir, our file is first. Explicit logger levels still help if both configs are merged in some scenarios. |
58+
| Startup script does not copy Logback files to writable config dir | The startup script in the repo (`docker/bin/zookeeperStart.sh`) is updated to copy `logback.xml` and `logback-quiet.xml` from `/conf` to `$ZOOCFGDIR` alongside the existing log4j and env.sh copies. Rebuilding the image that includes this script is required for the fix to take effect. |
59+
| Need to switch to ERROR at runtime without redeploy | Not supported in this change; would require env/CRD and `-Dlogback.configurationFile` in the image. Document as a possible future improvement. |
60+
61+
## Migration Plan
62+
63+
- **Deployment:** No migration steps for the operator. New ConfigMap entries are additive; existing keys and mounts unchanged. The **image** that runs the ZooKeeper container must include the updated startup script (copying logback.xml and logback-quiet.xml); rebuild and push the image, then roll out the new image to the StatefulSet.
64+
- **Rollout:** On next rollout with the updated image, pods will copy `logback.xml` and `logback-quiet.xml` to the config dir and the JVM will load `logback.xml` from that dir. No data or API migration.
65+
- **Rollback:** Revert the operator change and the startup script change; redeploy the previous image. ConfigMap will no longer contain the Logback keys; script will no longer copy them. Logback will fall back to default or another config on the classpath (e.g. from a JAR); behavior returns to whatever it was before (e.g. DEBUG).
66+
67+
## Open Questions
68+
69+
- **Optional:** Add a ZookeeperCluster field (e.g. `spec.logging.level: Info | Error`) and set `LOG_BACK_CONFIG_FILE` or JVM `-Dlogback.configurationFile` in env so the image can choose the quiet file without changing the image. Defer until someone needs it.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Proposal: Add Logback config so ZooKeeper logs at INFO
2+
3+
## Why
4+
5+
ZooKeeper pods managed by the operator log at DEBUG level, producing very high log volume from `org.apache.zookeeper` and `org.eclipse.jetty` (CommitProcessor, DataTree, NettyServerCnxn, Jetty lifecycle, etc.). This makes logs hard to use in production and can impact performance and log storage. The operator already generates `log4j.properties` and `log4j-quiet.properties`, but the ZooKeeper server image uses **Logback** as its SLF4J backend (logback-classic, logback-core in `lib/`), not Log4j, so those files are never read. To reduce log level to INFO we must provide a Logback configuration that is actually used at runtime.
6+
7+
## What Changes
8+
9+
- **Operator ConfigMap**: Generate and include `logback.xml` (root and key loggers at INFO) and `logback-quiet.xml` (root and key loggers at ERROR) in the ZookeeperCluster ConfigMap, alongside existing log4j files.
10+
- **Logback content**: Both files use a CONSOLE appender with the same pattern as upstream ZooKeeper (`%d{ISO8601} [myid:%X{myid}] - %-5p ...`). Explicit `<logger>` entries for `org.apache.zookeeper` and `org.eclipse.jetty` at the chosen level so DEBUG from those packages is suppressed regardless of any default config.
11+
- **No removal**: Existing `log4j.properties` and `log4j-quiet.properties` remain for images or setups that use Log4j; no breaking change to the ConfigMap shape or mount paths.
12+
- **Runtime behavior**: When the pod’s config dir (e.g. `/data/conf` or `/conf`) is on the classpath before other resources, Logback will load `logback.xml` from that dir and apply INFO (or ERROR when using the quiet variant), reducing log volume to a manageable level.
13+
14+
## Capabilities
15+
16+
### New Capabilities
17+
18+
- **zookeeper-logback-config**: The operator supplies Logback configuration (logback.xml and an optional quiet variant) so that ZooKeeper servers using Logback at runtime log at INFO by default, with an option for a quieter (ERROR) profile. Covers generation of the XML configs, inclusion in the ConfigMap, and the contract that the chosen file is on the server classpath so Logback picks it up.
19+
20+
### Modified Capabilities
21+
22+
- _(None. No existing specs in `openspec/specs/`; this is a new capability only.)_
23+
24+
## Impact
25+
26+
- **Code**: `pkg/zk/generators.go` — new helpers to generate logback.xml and logback-quiet.xml strings; ConfigMap `Data` extended with `logback.xml` and `logback-quiet.xml`.
27+
- **APIs**: No change to ZookeeperCluster CRD or public API.
28+
- **Dependencies**: None; no new libraries.
29+
- **Systems**: Pods that use the operator’s ConfigMap and a ZooKeeper image with Logback on the classpath will start using the new config on next rollout; config dir must be on the classpath (already true for the current startup flow). Images that use Log4j continue to rely on existing log4j.* properties.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Spec: zookeeper-logback-config
2+
3+
The operator supplies Logback configuration so that ZooKeeper servers using Logback at runtime log at INFO by default, with an optional quieter (ERROR) profile.
4+
5+
## ADDED Requirements
6+
7+
### Requirement: Operator generates default Logback config at INFO
8+
9+
The operator SHALL generate a Logback configuration file named `logback.xml` with root logger and CONSOLE appender at INFO level. The configuration SHALL set explicit logger level to INFO for the packages `org.apache.zookeeper` and `org.eclipse.jetty` so that DEBUG output from those packages is not emitted.
10+
11+
#### Scenario: Default config caps ZooKeeper and Jetty at INFO
12+
13+
- **WHEN** a ZookeeperCluster is reconciled and the ConfigMap is generated
14+
- **THEN** the ConfigMap SHALL contain a key `logback.xml` whose value is valid Logback XML with `<root level="INFO">` and `<logger name="org.apache.zookeeper" level="INFO"/>` and `<logger name="org.eclipse.jetty" level="INFO"/>`
15+
16+
#### Scenario: Default config uses CONSOLE appender with ZooKeeper pattern
17+
18+
- **WHEN** the generated `logback.xml` is inspected
19+
- **THEN** it SHALL contain a CONSOLE appender with a pattern that includes `%d{ISO8601}`, `[myid:%X{myid}]`, and `%-5p` so that log format is consistent with upstream ZooKeeper
20+
21+
### Requirement: Operator generates quiet Logback config at ERROR
22+
23+
The operator SHALL generate a Logback configuration file named `logback-quiet.xml` with root logger and CONSOLE appender at ERROR level. The configuration SHALL set explicit logger level to ERROR for `org.apache.zookeeper` and `org.eclipse.jetty`.
24+
25+
#### Scenario: Quiet config caps all at ERROR
26+
27+
- **WHEN** a ZookeeperCluster is reconciled and the ConfigMap is generated
28+
- **THEN** the ConfigMap SHALL contain a key `logback-quiet.xml` whose value is valid Logback XML with `<root level="ERROR">` and `<logger name="org.apache.zookeeper" level="ERROR"/>` and `<logger name="org.eclipse.jetty" level="ERROR"/>`
29+
30+
### Requirement: Logback configs are in the cluster ConfigMap
31+
32+
The operator SHALL include both `logback.xml` and `logback-quiet.xml` in the same ConfigMap that is used for the ZookeeperCluster (the one mounted at the config directory). No separate volume or mount SHALL be required.
33+
34+
#### Scenario: ConfigMap contains both Logback files
35+
36+
- **WHEN** the ZookeeperCluster ConfigMap is listed
37+
- **THEN** its `data` SHALL include the keys `logback.xml` and `logback-quiet.xml` in addition to existing keys (e.g. `zoo.cfg`, `env.sh`, `log4j.properties`, `log4j-quiet.properties`)
38+
39+
### Requirement: Config directory is on server classpath
40+
41+
For the Logback configuration to take effect, the ZooKeeper server process SHALL have the config directory (the directory where `logback.xml` is placed, e.g. `/data/conf` or `/conf`) on its Java classpath before any JAR or resource that might contain another `logback.xml`. The startup script SHALL copy `logback.xml` and `logback-quiet.xml` from the mounted config source into that directory before starting the server, so that Logback discovers them.
42+
43+
#### Scenario: Logback discovers operator config when config dir is first on classpath
44+
45+
- **WHEN** the ZooKeeper server starts with the operator’s ConfigMap mounted and the config directory is the first element on the classpath
46+
- **THEN** Logback SHALL load `logback.xml` from that directory and the effective log level for `org.apache.zookeeper` and `org.eclipse.jetty` SHALL be INFO (or ERROR if the process is configured to use `logback-quiet.xml`)
47+
48+
### Requirement: Startup script copies Logback configs to writable config directory
49+
50+
The ZooKeeper startup script (e.g. `zookeeperStart.sh`) SHALL copy `logback.xml` and `logback-quiet.xml` from the mounted config source (e.g. `/conf`) to the writable config directory used at runtime (e.g. `/data/conf`, i.e. `ZOOCFGDIR`) before invoking the ZooKeeper server, in the same way it copies `log4j.properties`, `log4j-quiet.properties`, and `env.sh`. This ensures the files are present on the classpath when the JVM starts.
51+
52+
#### Scenario: Script copies Logback files alongside other config files
53+
54+
- **WHEN** the pod starts and the startup script runs before `zkServer.sh --config $ZOOCFGDIR start-foreground`
55+
- **THEN** the script SHALL copy `/conf/logback.xml` and `/conf/logback-quiet.xml` to `$ZOOCFGDIR` (e.g. `/data/conf`), so that both files exist in the same directory as `zoo.cfg`, `log4j.properties`, `log4j-quiet.properties`, and `env.sh`

0 commit comments

Comments
 (0)