Skip to content

#3732 fix: DefaultLogger no longer creates ./log when no FileHandler is configured#3734

Merged
robfrank merged 3 commits into
mainfrom
fix/3732-default-logger-log-directory
Mar 26, 2026
Merged

#3732 fix: DefaultLogger no longer creates ./log when no FileHandler is configured#3734
robfrank merged 3 commits into
mainfrom
fix/3732-default-logger-log-directory

Conversation

@robfrank

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes DefaultLogger always tries to create .log directory #3732
  • DefaultLogger.init() was unconditionally creating ./log at startup regardless of logging configuration, producing a spurious Cannot create log directory: /./log error when the path wasn't writable
  • Fix peeks at the pending log configuration to find java.util.logging.FileHandler.pattern (without loading it into the LogManager), pre-creates only that directory, then loads the config — no directory is created when no FileHandler is configured

Test plan

  • LoggerTest#noLogDirectoryCreatedWithConsoleOnlyConfig — console-only config → no FileHandler.pattern registered, no spurious directory creation
  • LoggerTest#logDirectoryCreatedFromConfiguredFileHandlerPattern — custom FileHandler.pattern pointing to a temp dir → that dir is created before config is loaded
  • LoggerTest#customLogger — existing test still passes

All 3 tests pass (mvn test -pl engine -Dtest=LoggerTest).

🤖 Generated with Claude Code

…figured

Fixes #3732. Previously DefaultLogger.init() hardcoded creation of the ./log
directory at every startup, printing an error when not writable even if the
user had configured console-only logging. The fix peeks at the pending log
configuration, finds the FileHandler.pattern (if any), and pre-creates only
that directory — nothing is created when no FileHandler is configured.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a bug in DefaultLogger where it would attempt to create a ./log directory on startup regardless of whether file-based logging was configured. This led to unnecessary directory creation and potential errors in environments where the default path was not writable. The solution involves intelligently parsing the logging configuration to identify if a FileHandler is present and, if so, pre-creating only the necessary directory specified by its pattern before the full logging configuration is loaded. This ensures that log directories are only created when required and at the correct location.

Highlights

  • Unconditional Log Directory Creation Fixed: Resolved an issue where DefaultLogger.init() would unconditionally attempt to create a ./log directory at startup, leading to errors if the path was unwritable, even when file-based logging was not configured.
  • Conditional Log Directory Creation: Modified the logger initialization to only pre-create a log directory if a java.util.logging.FileHandler.pattern is explicitly configured, preventing spurious directory creation.
  • Configuration Parsing: Implemented logic to peek at the pending logging configuration to extract the FileHandler.pattern without fully loading the configuration into the LogManager, allowing for targeted directory creation.
  • New Regression Tests: Added two new regression tests (noLogDirectoryCreatedWithConsoleOnlyConfig and logDirectoryCreatedFromConfiguredFileHandlerPattern) to LoggerTest to ensure the fix behaves as expected for both console-only and file-handler configurations.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@claude

claude Bot commented Mar 26, 2026

Copy link
Copy Markdown

Code Review

Good fix for issue #3732 — the approach of peeking at the configuration before loading it is sound and the three-location fallback chain mirrors installCustomFormatter() correctly. A few items to address:


Bug: %% escape must be resolved before other substitutions

File: engine/src/main/java/com/arcadedb/log/DefaultLogger.java, createLogDirectoryFromConfig()

final String resolved = pattern
    .replace("%t", System.getProperty("java.io.tmpdir", "/tmp"))
    .replace("%h", System.getProperty("user.home", "/"))
    .replace("%g", "0")
    .replace("%u", "0")
    .replace("%%", "%");   // ← should be FIRST, not last

JUL uses %% as an escape for a literal %. Because String.replace works on the result of the previous call, a pattern like %%t is first transformed by the %t replacement: %%t%<tmpdir>, which is wrong. The correct order is to convert %% → some non-conflicting placeholder (or directly to %) first, then expand the single-percent substitutions:

final String resolved = pattern
    .replace("%%", "%")   // ← expand escape first
    .replace("%t", System.getProperty("java.io.tmpdir", "/tmp"))
    .replace("%h", System.getProperty("user.home", "/"))
    .replace("%g", "0")
    .replace("%u", "0");

Code style: fully qualified type name inside method body

File: engine/src/main/java/com/arcadedb/log/DefaultLogger.java, loadPatternFromStream()

final java.util.Properties props = new java.util.Properties();

Per the project guidelines, fully qualified names should be avoided in favour of an import statement. java.util.Properties should be added to the import block and referenced as Properties.

Same issue appears in the test file (java.util.logging.LogManager, java.util.logging.Logger, java.util.logging.FileHandler referenced without imports in new methods).


Unwanted file committed at repository root

File: 3732-default-logger-log-directory.md (new file)

This design-notes document should not be committed to the repository. Its content duplicates the PR description. Merge it into the PR body or discard it — it would clutter the repository root for all future contributors.


Test: noLogDirectoryCreatedWithConsoleOnlyConfig doesn't verify its stated postcondition

The test name promises that no directory is created, but the only assertion checks that FileHandler.pattern is absent from LogManager — which is a weaker proxy. To make the regression test truly catch a recurrence of issue #3732, add a direct filesystem assertion, for example:

final File spuriousLogDir = new File("./log");
final boolean existedBefore = spuriousLogDir.exists();
// ... run logger.init() ...
if (!existedBefore)
    assertThat(spuriousLogDir).doesNotExist();

Minor: prefer try-with-resources for classpath stream

findConfiguredFileHandlerPattern() uses an explicit try/finally for the classpath InputStream. The config/ branch below it already uses try-with-resources; the classpath branch could do the same for consistency:

try (InputStream stream = getClass().getClassLoader().getResourceAsStream(FILE_LOG_PROPERTIES)) {
    if (stream != null)
        return loadPatternFromStream(stream);
}

Overall the fix is well-motivated and the general approach is correct. Addressing the %%-before-%x ordering is the most important change; the rest are style/quality improvements.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request resolves issue #3732 by modifying the DefaultLogger to prevent the unconditional creation of a ./log directory. The init() method has been refactored to dynamically determine and pre-create the log directory only if a java.util.logging.FileHandler.pattern is explicitly configured, resolving issues with unwritable paths and custom logging setups. New helper methods were introduced to parse the logging configuration before full initialization. Two regression tests have been added to validate both scenarios: no directory creation for console-only configurations and correct directory creation for file-based logging. The only feedback is to improve the exception handling in createLogDirectoryFromConfig by logging the caught exception instead of silently ignoring it, to aid in debugging.

Comment thread engine/src/main/java/com/arcadedb/log/DefaultLogger.java Outdated
- Fix %% escape ordering: use a null-char placeholder so that %%t is not
  erroneously expanded to <tmpdir> by the %t substitution
- Import java.util.Properties instead of using the fully-qualified name
- Switch classpath InputStream to try-with-resources for consistency
- Log the caught exception message in createLogDirectoryFromConfig
- Strengthen regression test: assert ./log was not created (not just that
  FileHandler.pattern is absent from LogManager)
- Remove tracking doc from repository root

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@robfrank

Copy link
Copy Markdown
Collaborator Author

Addressed all review feedback in the follow-up commit:

  • %% escape ordering — the reviewer's suggestion of moving %%→% first was itself still broken: %%t%t<tmpdir>. Fixed with a null-char placeholder so escaped sequences are never touched by the single-percent substitutions.
  • java.util.Properties fully-qualified name — replaced with an import.
  • try/finally → try-with-resources for the classpath InputStream branch, matching the config/ branch below it.
  • Caught exception now loggedSystem.err.println(e.getMessage()) instead of silent // IGNORE.
  • Stronger test postconditionnoLogDirectoryCreatedWithConsoleOnlyConfig now adds a filesystem assertion: if ./log didn't exist before init(), it asserts it still doesn't after.
  • Tracking doc removed from repository root.

@claude

claude Bot commented Mar 26, 2026

Copy link
Copy Markdown

Code Review: fix: DefaultLogger no longer creates ./log when no FileHandler is configured

The fix is well-motivated, correctly scoped, and targets the real regression from issue #3732. The overall approach — inspecting the configuration before handing it to LogManager to derive the directory to pre-create — is sound. The three-location fallback chain in findConfiguredFileHandlerPattern() correctly mirrors the one in installCustomFormatter(). Two regression tests are added and the initialized guard remains in place.


Issues to Address

Medium: Fully qualified names in test code

LoggerTest.java lines ~71, 128, 133

Per project guidelines ("don't use fully qualified names if possible, always import the class and just use the name"), the test uses java.util.logging.LogManager and java.util.logging.Logger as FQNs. Since FileHandler and Handler from java.util.logging are already imported in the file, LogManager and Logger should be added to the import block and referenced by short name.

// Instead of:
java.util.logging.LogManager.getLogManager().readConfiguration(stream);
java.util.logging.Logger.getLogger("").getHandlers()

// Use:
import java.util.logging.LogManager;
import java.util.logging.Logger;
// ...
LogManager.getLogManager().readConfiguration(stream);
Logger.getLogger("").getHandlers()

Medium: Primary test assertion targets LogManager state instead of filesystem

LoggerTest.java noLogDirectoryCreatedWithConsoleOnlyConfig

The first assertion checks that FileHandler.pattern is absent from LogManager. This is an indirect proxy. The actual regression from #3732 was a filesystem side-effect (./log directory being created). The filesystem check that follows is the right regression guard and should be the primary assertion, not the secondary one.

While the conditional guard (!existedBefore) is pragmatically necessary to avoid flakiness in environments where ./log pre-exists, consider documenting this in a comment so future readers understand why the assertion is conditional.


Low: %t/%h substitution path is untested

createLogDirectoryFromConfig() contains substitution logic for %t, %h, %g, and %u. The new test only exercises an absolute path (/tmp/arcade-test-logs/), so the substitution code path is never reached by any test. A third test case with a pattern like %t/arcade-test-%g.log would cover this branch and validate that System.getProperty("java.io.tmpdir") is correctly resolved.


Low: initialized = true is set before work completes (pre-existing)

DefaultLogger.java

This is a pre-existing issue (not introduced by this PR), but worth noting: initialized = true is set before createLogDirectoryFromConfig() and installCustomFormatter() execute. A second concurrent caller seeing the flag will return early with an incompletely-configured logger. Since this PR adds more work after the flag is set, the window for this race is slightly larger. Consider moving initialized = true to after both methods complete.


Minor: deleteTree silently swallows failures

LoggerTest.java cleanup helper

Files.walk(root).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);

File::delete returns a boolean and silently discards failures (especially relevant on Windows where an open FileHandler could lock the file). Consider using Files.deleteIfExists(path) inside a try-catch to surface unexpected failures.


What Looks Good

  • The fix correctly eliminates the unconditional new File("./log") creation — this is the right surgical change.
  • The %%\u0000 placeholder trick is correctly implemented; the substitution order is correct.
  • createLogDirectoryFromConfig() is called before installCustomFormatter() — correct ordering.
  • Both new tests use AssertJ (assertThat(...)) consistent with project style.
  • No new dependencies introduced.
  • Production code correctly imports Properties and avoids FQNs.
  • The mkdirs() call is appropriately guarded by parentDir != null.

The fix is almost ready. The main items to address are the import-style violations in the test class and optionally a third test case covering the %t/%h substitution path.

- Add logDirectoryCreatedWithTmpDirSubstitution test: verifies %t (java.io.tmpdir)
  is correctly resolved when pre-creating the log directory
- Clarify inline that java.util.logging.LogManager and java.util.logging.Logger
  must remain fully-qualified due to naming conflict with com.arcadedb.log.*
  imports required by the existing customLogger() test (Java has no import aliases)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@robfrank

Copy link
Copy Markdown
Collaborator Author

Added logDirectoryCreatedWithTmpDirSubstitution — uses %t in the FileHandler.pattern to exercise the java.io.tmpdir substitution path and asserts the resolved subdirectory is created.

Regarding the fully-qualified java.util.logging.LogManager and java.util.logging.Logger in the test class: both conflict with com.arcadedb.log.LogManager and com.arcadedb.log.Logger, which are required by the existing customLogger() test. Java has no import aliases, so those two must stay fully-qualified — added an inline comment to make this explicit.

@claude

claude Bot commented Mar 26, 2026

Copy link
Copy Markdown

Code Review

Good fix for issue #3732. The approach of peeking at configuration before loading it into LogManager is correct, and the three-location fallback chain in findConfiguredFileHandlerPattern() properly mirrors installCustomFormatter(). The Gemini review already caught and the author addressed most issues; here are my observations on the current diff state.


What Looks Good

  • %%\u0000 placeholder is correctly implemented — %%t\u0000t (not touched by %t substitution) → %t (correct literal). This is better than naively replacing %%% first.
  • Properties is properly imported and not used as a fully-qualified name in production code.
  • Three regression tests cover the major scenarios: console-only, absolute path FileHandler, and %t-based substitution.
  • FQN usage of java.util.logging.LogManager and java.util.logging.Logger in tests is justified by the import conflict with com.arcadedb.log.LogManager and com.arcadedb.log.Logger. The inline comment makes this explicit — this is the right call.
  • getParentFile() == null guard is present and correct.
  • No new dependencies introduced.

Issues to Address

Medium: deleteTree silently swallows all deletion failures

Files.walk(root).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);

File::delete returns a boolean and never throws — failures are silently discarded. On Windows, an open FileHandler can hold a lock on log files, causing test cleanup to fail silently and leaving stale temp directories. Prefer Files.deleteIfExists(Path) in a proper lambda:

Files.walk(root)
    .sorted(Comparator.reverseOrder())
    .forEach(p -> {
        try { Files.deleteIfExists(p); }
        catch (IOException e) { System.err.println("Could not delete " + p + ": " + e.getMessage()); }
    });

Low: initialized = true set before work completes (pre-existing, but widened)

initialized = true is set at the top of init() before createLogDirectoryFromConfig() and installCustomFormatter() execute. A concurrent caller will see the flag and skip initialization, leaving the logger partially configured. This PR adds more work after the flag is set, slightly widening the race window. Not a blocker since it's pre-existing, but worth a follow-up issue.


Low: noLogDirectoryCreatedWithConsoleOnlyConfig — LogManager assertion may be misleading

The assertion:

assertThat(java.util.logging.LogManager.getLogManager().getProperty("java.util.logging.FileHandler.pattern")).isNull();

checks LogManager state, but installCustomFormatter() is called inside init() and re-reads the config file into LogManager. So this assertion actually tests a side-effect of installCustomFormatter(), not directly the bug being fixed. The filesystem assertion added below it (assertThat(defaultLogDir).doesNotExist()) is the real regression guard. Consider reordering or adding a comment clarifying what each assertion covers.


Nit: Exception type in createLogDirectoryFromConfig is broader than needed

} catch (final Exception e) {
    System.err.println("Error creating log directory: " + e.getMessage());
}

logDir.mkdirs() only throws SecurityException (a RuntimeException). Catching Exception is broader than necessary. SecurityException or no catch (letting it propagate) would be more precise, though catching broadly here is not harmful for a startup routine.


Overall the fix is correct and well-tested. The deleteTree cleanup issue is the most worth addressing before merging. Everything else is low-priority.

@robfrank robfrank changed the title fix: DefaultLogger no longer creates ./log when no FileHandler is configured #3732 fix: DefaultLogger no longer creates ./log when no FileHandler is configured Mar 26, 2026
@robfrank robfrank merged commit f35abdc into main Mar 26, 2026
19 of 23 checks passed
@robfrank robfrank deleted the fix/3732-default-logger-log-directory branch March 26, 2026 13:56
@codacy-production

codacy-production Bot commented Mar 26, 2026

Copy link
Copy Markdown

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
-8.76% 72.09%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (cb82fb7) 115374 84669 73.39%
Head commit (ca36fb3) 146368 (+30994) 94587 (+9918) 64.62% (-8.76%)

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 (#3734) 43 31 72.09%

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%

See your quality gate settings    Change summary preferences

@codecov

codecov Bot commented Mar 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.79070% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.56%. Comparing base (cb82fb7) to head (ca36fb3).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
.../src/main/java/com/arcadedb/log/DefaultLogger.java 62.79% 12 Missing and 4 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3734   +/-   ##
=======================================
  Coverage   64.56%   64.56%           
=======================================
  Files        1580     1580           
  Lines      115374   115414   +40     
  Branches    24426    24432    +6     
=======================================
+ Hits        74488    74518   +30     
  Misses      30733    30733           
- Partials    10153    10163   +10     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@robfrank robfrank added this to the 26.4.1 milestone Apr 22, 2026
robfrank added a commit that referenced this pull request May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DefaultLogger always tries to create .log directory

1 participant