fix(#4451): configurable server log directory for read-only root filesystems#4462
Conversation
…ged config and scripts
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 0 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Code Review
This pull request makes the server log directory configurable via the "arcadedb.server.logsDirectory" system property or "ARCADEDB_LOG_DIR" environment variable, resolving issues on read-only root filesystems. The changes include updates to the logger initialization, configuration definitions, startup scripts, and tests. The review feedback points out a potential issue in the Windows batch script ("server.bat") where an unquoted path containing spaces could cause JVM startup failures, and suggests wrapping the JVM argument in double quotes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
|
||
| rem Optional: relocate log files to a writable directory (e.g. read-only root filesystems). | ||
| rem ARCADEDB_LOG_DIR maps to arcadedb.server.logsDirectory used by config/arcadedb-log.properties. | ||
| if not "%ARCADEDB_LOG_DIR%"=="" set JAVA_OPTS_SCRIPT=%JAVA_OPTS_SCRIPT% -Darcadedb.server.logsDirectory=%ARCADEDB_LOG_DIR% |
There was a problem hiding this comment.
If the ARCADEDB_LOG_DIR environment variable contains spaces (which is common on Windows, e.g., a path under Program Files), the unquoted expansion in set JAVA_OPTS_SCRIPT=%JAVA_OPTS_SCRIPT% -Darcadedb.server.logsDirectory=%ARCADEDB_LOG_DIR% will cause the JVM to fail to start due to argument splitting.\n\nWrapping the -D option in double quotes ensures that the path is passed as a single argument to the JVM even if it contains spaces.
if not "%ARCADEDB_LOG_DIR%"=="" set JAVA_OPTS_SCRIPT=%JAVA_OPTS_SCRIPT% "-Darcadedb.server.logsDirectory=%ARCADEDB_LOG_DIR%"
There was a problem hiding this comment.
Applied in 7656d56. Quoted the argument as "-Darcadedb.server.logsDirectory=%ARCADEDB_LOG_DIR%" so a path containing spaces (e.g. under Program Files) is passed to the JVM as a single argument. The Unix server.sh is left unquoted to stay consistent with the rest of $JAVA_OPTS_SCRIPT, which is intentionally word-split at the final exec.
Code ReviewOverall: A well-motivated fix for a real Kubernetes deployment problem. The approach is correct - resolving A few issues worth addressing before merge: Medium:
|
|
Dispositions for the review, pushed in 7656d56: Windows batch unquoted path (Medium) - fixed. Quoted the Default drift between Remove Reflection to test a private method (Low) - keeping reflection.
|
Code Review - PR #4462: Configurable Server Log DirectoryOverviewThis PR fixes a legitimate production issue: ArcadeDB crashes on Kubernetes pods with The approach is sound and backward-compatible. A few issues worth addressing before merge: Bug: Unquoted
|
|
Second-pass dispositions (commit 32ae1f9):
(The ${ARCADEDB_LOG_DIR:+"-Darcadedb.server.logsDirectory=$ARCADEDB_LOG_DIR"}Verified: a path with spaces becomes a single argument, and when unset no argument (not even an empty one) is added. Two constants in sync - comment clarified. Expanded the
Reflection in tests (Low) - keeping.
|
Code ReviewSummary: A well-targeted fix for a genuine Kubernetes deployment issue. The core approach - intercepting the JUL config stream before it reaches LogManager and resolving placeholders via SystemVariableResolver - is the correct insertion point given the bootstrap ordering problem. The implementation is clean, backward-compatible, and fixes a pre-existing stream leak as a bonus. Issue 1 (medium) - Undocumented behavior: any unresolved placeholder falls back to ./log resolveConfigurableLogDir delegates to SystemVariableResolver.resolveSystemVariables(pattern, DEFAULT_LOG_DIR). The second argument is the per-placeholder fallback applied to EVERY unresolved placeholder in the pattern, not just arcadedb.server.logsDirectory. Concretely: if an operator has a custom arcadedb-log.properties with a non-standard placeholder, before this PR JUL receives the literal string unchanged. After this PR, unrecognized placeholders are silently replaced with ./log. This is arguably better behavior, but the Javadoc on resolveConfigurableLogDir should clarify this so operators with custom properties files understand the post-upgrade semantics. Issue 2 (low) - Properties.store() drops original file comments The round-trip through Properties.store() in installCustomFormatter() strips every comment from arcadedb-log.properties before the byte stream reaches LogManager. JUL ignores comment lines anyway so key/value behavior is unaffected today. Properties.store(out, null) also always prepends a timestamp header. Neither breaks anything, but a brief note in installCustomFormatter() would help future maintainers. Issue 3 (low) - A direct assertion test would catch constant drift more mechanically DEFAULT_LOG_DIR in DefaultLogger and the default for SERVER_LOGS_DIRECTORY in GlobalConfiguration both hard-code ./log. CI catches drift via two independent test assertions today. A single test directly comparing them would be more self-documenting and fail at the right abstraction level. Suggestion only, not a blocker. What works well:
The main actionable item is a Javadoc clarification on resolveConfigurableLogDir noting that any unresolved placeholder falls back to ./log, so operators with custom properties files understand the behavior after upgrading. |
…trip in DefaultLogger
|
Third-pass dispositions (commit aa90535): Issue 1 (Medium) - Javadoc clarified. One precision note: the Issue 2 (Low) - comment added. Issue 3 (Low) - no change. This is explicitly non-blocking and CI already catches drift via the two independent assertions. A single direct comparison would have to read |
Code Review: fix(#4451) - Configurable Server Log DirectoryOverall: Well-structured fix for a real operational pain point. The approach is sound, backward compatibility is preserved, and the bonus fix of the unclosed-stream leak in Issues and Suggestions1. Fragile dual-source-of-truth for the default log dir
// In GlobalConfigurationTest or LoggerTest:
assertThat(GlobalConfiguration.SERVER_LOGS_DIRECTORY.getValueAsString())
.as("DefaultLogger fallback and GlobalConfiguration default must agree")
.isEqualTo(DEFAULT_LOG_DIR_value_accessed_via_reflection);Alternatively, have 2. Reflection to test a private method is a code smell
private static String invokeResolveLogDir(final String pattern) throws ReflectiveOperationException {
final java.lang.reflect.Method m = DefaultLogger.class.getDeclaredMethod("resolveConfigurableLogDir", String.class);
m.setAccessible(true);
return (String) m.invoke(null, pattern);
}Since the method is pure (no side-effects, no access to internal state), making it package-private would be cleaner and remove the need for reflection in tests. The test class is already in the same package structure ( 3. Shell quoting in
|
…default drift test
db078ce to
41d7466
Compare
|
Fourth-pass dispositions (commit 41d7466): Issues 1 + 2 (drift test + reflection) - both fixed with one structural change. I made assertThat(GlobalConfiguration.SERVER_LOGS_DIRECTORY.getDefValue()).isEqualTo(DefaultLogger.DEFAULT_LOG_DIR);Using Issue 3 (shell array) - declined. Issue 4 (Properties ISO-8859-1) - declined, not a regression. JUL's own Issue 5 (design doc) - keeping. As covered in the prior passes: the cited CLAUDE.md instruction is not in this repo's CLAUDE.md, and 38 Issue 6 (comment what-vs-why) - keeping. The comment documents the non-obvious WHY (the bootstrap-ordering constraint that forces interception here rather than a direct |
Code Review - fix(#4451): configurable server log directory for read-only root filesystemsOverviewThis PR addresses a real Kubernetes deployment problem: JUL's The approach is sound and the implementation is clean. A few observations: Positives
Issues1. This file is an internal implementation document that duplicates the PR description almost verbatim. Design docs like this belong in PRs (where they're already captured), not in the repository. If there's a need for operator-facing documentation, the right place is the existing docs site or a 2. The key name starts with // Placed in engine (not server) because logging initializes before the server root path is
// set. Must stay in sync with DefaultLogger.DEFAULT_LOG_DIR (enforced by DefaultLoggerLogDirTest).
SERVER_LOGS_DIRECTORY("arcadedb.server.logsDirectory", SCOPE.JVM, ...),3. The method's Javadoc explains what it does but not why it's package-private. A one-liner noting it's accessible to /** ... package-private for {@link DefaultLoggerLogDirTest}. */
static String resolveConfigurableLogDir(final String pattern) {4. if not "%ARCADEDB_LOG_DIR%"=="" set JAVA_OPTS_SCRIPT=%JAVA_OPTS_SCRIPT% "-Darcadedb.server.logsDirectory=%ARCADEDB_LOG_DIR%"If Minor nits
SummaryThe fix is correct, the core logic is clean, and the test coverage is thorough. The main ask is dropping |
Code ReviewOverall: A well-targeted fix for a real Kubernetes deployment problem. The approach - resolving placeholders before handing the config stream to JUL - is exactly the right intercept point. Backward compatibility is preserved, the pre-existing unclosed-stream leak is fixed as a bonus, and the core happy-path test coverage is solid. The docs/ file follows existing project convention The previous reviewer flagged docs/4451-configurable-log-directory.md as clutter, but the docs/ directory already holds 44 per-issue design documents using the same naming scheme (e.g. 4274-cluster-topology-consistency.md, 4326-polyglot-js-injection.md). Adding 4451-configurable-log-directory.md is consistent with the project established practice - no change needed here. Bug: stale class name in comment DefaultLogger.java, line 51: No such class exists - the test was added to LoggerTest. The comment should read LoggerTest (or simply be dropped, since it would rot again if the test moves). Small but misleading for future readers. Missing test: explicit fallback to ./log when placeholder is unset The PR description says 'unset placeholder falls back to ./log' is tested, but the diff adds only one new test method (fileHandlerPatternResolvesConfigurableLogDir), which covers the case where the system property IS set. The fallback path (no system property, no env var, GlobalConfiguration not yet initialised) is not exercised by any visible test. A simple unit test on resolveConfigurableLogDir covering that case would close this gap: @Test
void resolveConfigurableLogDir_fallsBackToDefaultWhenUnset() {
System.clearProperty("arcadedb.server.logsDirectory");
final String result = DefaultLogger.resolveConfigurableLogDir("${arcadedb.server.logsDirectory}/arcadedb.log");
// GlobalConfiguration.SERVER_LOGS_DIRECTORY default is "./log", so result should be "./log/arcadedb.log"
assertThat(result).isEqualTo("./log/arcadedb.log");
}Observation: GlobalConfiguration default silently provides the answer before DEFAULT_LOG_DIR is ever needed The comment on DEFAULT_LOG_DIR (lines 48-51) is accurate - the constant acts as a safety net for the very narrow window when GlobalConfiguration is still initialising. In normal operation, SystemVariableResolver.resolveVariable walks system property -> env var -> GlobalConfiguration.findByKey, so SERVER_LOGS_DIRECTORY own default of './log' means the placeholder almost always resolves successfully and DEFAULT_LOG_DIR is never reached. The fallback is the right defence-in-depth choice; the docstring on resolveConfigurableLogDir could mention that GlobalConfiguration default is the primary fallback path, with DEFAULT_LOG_DIR covering only the static-init bootstrap window. Code quality positives
Summary Two actionable items before merge:
Everything else is solid. |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 0 |
🟢 Coverage 100.00% diff coverage · -7.36% coverage variation
Metric Results Coverage variation ✅ -7.36% coverage variation Diff coverage ✅ 100.00% diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (37682c5) 124358 92579 74.45% Head commit (41d7466) 156061 (+31703) 104701 (+12122) 67.09% (-7.36%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#4462) 15 15 100.00% Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4462 +/- ##
==========================================
+ Coverage 65.25% 65.67% +0.41%
==========================================
Files 1612 1612
Lines 124358 124373 +15
Branches 26924 26926 +2
==========================================
+ Hits 81155 81680 +525
+ Misses 31807 31187 -620
- Partials 11396 11506 +110 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Summary
Fixes #4451. The server crashed at startup with
java.nio.file.FileSystemException: ./log/arcadedb.log.0.lck: Read-only file systemon KubernetesreadOnlyRootFilesystempods, because the JULFileHandlerpattern is hardcoded./log/arcadedb.log(relative to the working directory) and is opened duringGlobalConfigurationstatic init - before the server root path is set.The log directory is now configurable, defaulting to
./log(no behavior change for existing installs):DefaultLogger.resolveConfigurableLogDir()resolves${...}placeholders in theFileHandler.patternvia the existingSystemVariableResolver(system property -> env var ->GlobalConfiguration, default./log). It is wired into bothcreateLogDirectoryFromConfig()(directory pre-creation) andinstallCustomFormatter()(the pattern handed to JUL), so the two always agree.installCustomFormatter()now loads the config intoProperties, rewrites the pattern, and re-feeds JUL (this also closes a pre-existing unclosed-stream leak).GlobalConfiguration.SERVER_LOGS_DIRECTORY(arcadedb.server.logsDirectory, default./log).arcadedb-log.propertiesuses${arcadedb.server.logsDirectory}/arcadedb.log.server.sh/server.batforward an optionalARCADEDB_LOG_DIRenv var as-Darcadedb.server.logsDirectory=....Operators on read-only root filesystems set
ARCADEDB_LOG_DIR=/writable/mount/log(or the system property directly) to relocate logs to a writable mount. Companion to #4446 (configurable Raft storage directory).Test Plan
mvn -pl engine -Dtest=LoggerTest,GlobalConfigurationTest test- 18/18 pass, including the issue-DefaultLogger always tries to create .log directory #3732 regression tests proving the default./logbehavior and literal patterns are unchanged../log; literal pattern passes through unchanged; end-to-endinit()creates the resolved directory and JUL receives the resolved pattern.arcadedb-log.properties: with a read-only./logandarcadedb.server.logsDirectorypointing at a writable dir, the server logs to the writable dir, leaves./loguntouched, and does not throw the read-only exception.🤖 Generated with Claude Code