Skip to content

Add opt-in SLF4J facade logging (Slf4jLogger), keeping java.util.logging as default (#4276) - #5361

Merged
robfrank merged 5 commits into
ArcadeData:mainfrom
ruispereira:slf4j-facade-logging
Jul 27, 2026
Merged

Add opt-in SLF4J facade logging (Slf4jLogger), keeping java.util.logging as default (#4276)#5361
robfrank merged 5 commits into
ArcadeData:mainfrom
ruispereira:slf4j-facade-logging

Conversation

@ruispereira

Copy link
Copy Markdown
Contributor

Closes #4276 (opt-in first step).

Summary

Adds an opt-in Slf4jLogger that routes ArcadeDB's logs through the SLF4J facade, so an application embedding ArcadeDB receives the engine's logs in whatever backend it already uses (Logback, Log4j2, reload4j, or java.util.logging via slf4j-jdk14) — with no dependency exclusions.

It is deliberately non-disruptive: the default logger is unchanged (DefaultLogger, java.util.logging, with the existing text/ANSI and JSON formatting), so standalone and current deployments behave exactly as before. This matches the plan agreed in #4276; SLF4J can be made the default in a follow-up if you'd prefer.

How to enable

Startup, no code change:

-Darcadedb.log.impl=slf4j

or programmatically:

LogManager.instance().setLogger(new Slf4jLogger());

What changed

  • engine/.../log/Slf4jLogger.java (new) — a com.arcadedb.log.Logger that delegates to org.slf4j:
    • maps java.util.logging.Level onto SLF4J's five levels by numeric weight (SEVERE→ERROR, WARNING→WARN, INFO→INFO, FINE→DEBUG, else TRACE), so custom/intermediate JUL levels also resolve;
    • preserves the call-site contract of DefaultLogger: logger name resolved from the requester (String / Class / instance / com.arcadedb fallback), <context> prefixing, and printf-style (%s) message formatting (not SLF4J {} placeholders), which is how ArcadeDB messages are written;
    • skips formatting entirely when the level is disabled;
    • exposes the per-request correlation carried by LogManager (requestId, database, traceId, spanId) to the backend via MDC under arcadedb.* keys, saving and restoring any pre-existing values so the host application's MDC is left untouched.
  • engine/.../log/LogManager.java — selects the implementation from the new arcadedb.log.impl system property (default = DefaultLogger; slf4j = Slf4jLogger); falls back to DefaultLogger if construction fails, so a misconfiguration never silences logging.
  • engine/pom.xml — adds slf4j-api only. The engine pins no backend.
  • pom.xml — moves slf4j-jdk14 to test scope. It was a compile-scope dependency in the parent <dependencies>, so it leaked transitively to consumers of every library module (forcing exclusions). At test scope, module tests still log through JUL while nothing is imposed on downstream consumers.
  • examples/logging/ (new) — ready-to-use logback.xml and log4j2.xml (console + JSON + rolling file, configurable log directory) and a README.md mapping every native logging feature (arcadedb.server.logFormat, logIncludeTrace, logsDirectory, per-package levels, correlation) to its Logback/Log4j2 equivalent.

DefaultLogger, ArcadeDBServer, GlobalConfiguration and the existing logging tests are untouched.

Dependency hygiene

Embedding arcadedb-engine now pulls only slf4j-api; the embedder provides exactly one binding and needs no <exclusions>. To get plain JUL, add slf4j-jdk14 and SLF4J forwards to it.

Backward compatibility

Default behaviour is unchanged (JUL DefaultLogger), so there is no regression for standalone or existing embedders. SLF4J routing is entirely opt-in.

Testing

  • New Slf4jLoggerTest (12 tests): level mapping, printf substitution, <context> prefixing, logger-name resolution (String/Class/null), the fixed-arity overload, level gating, literal % without args, best-effort fallback when a format string doesn't match its args, throwable pass-through, and MDC publish/restore (including preserving a host value under an arcadedb.* key).
  • All existing logging tests pass unchanged (LoggerTest, DefaultLoggerLogDirTest, DefaultFormatterUnchangedTest, LogCorrelationContextTest, LogFormatterTraceTagTest, JsonLogFormatterTest, LogFormatterMessageFormatTest) — 48 logging tests green in total.
  • Full arcadedb-engine test suite: the only failure is a pre-existing one on main (LSMTreeSortedBuildCrashTest.crashAfterAttachmentLeavesSchemaUnpublishedAndAllowsRetry), reproduced identically on a clean checkout without this change — unrelated to logging.
  • Verified end-to-end with Logback on the classpath (no slf4j-jdk14): logs are emitted by Logback with correct level mapping, %s substitution, MDC correlation populated during the call and cleared afterwards.

Note

One small thing worth a maintainer's eye: the repo's .gitignore rule **/log also matches the source package com/arcadedb/log/, so the two new files there had to be git add -f'd. You may want to tighten that rule (e.g. anchor it to build output dirs) so new sources under that package aren't silently ignored.

…ing as default

Adds an Slf4jLogger that routes ArcadeDB's logs through the SLF4J facade, so an
application embedding ArcadeDB receives the engine's logs in whatever backend it
already uses (Logback, Log4j2, reload4j, or java.util.logging via slf4j-jdk14) with
no dependency exclusions.

It is opt-in and non-disruptive: the default logger remains DefaultLogger
(java.util.logging, with the existing text/ANSI and JSON formatting). Select SLF4J
at startup with -Darcadedb.log.impl=slf4j, or programmatically via
LogManager.setLogger(new Slf4jLogger()).

- engine depends only on slf4j-api (the facade); it pins no backend.
- Level mapping onto SLF4J's five levels; printf-style (%s) message formatting and
  <context> prefixing preserved; per-request correlation (requestId/database/
  traceId/spanId) exposed via MDC under arcadedb.* keys, saved/restored around the
  call so the host application's MDC is left untouched.
- Root pom: slf4j-jdk14 moved to test scope so the JUL binding never leaks
  transitively to consumers of the library modules.
- examples/logging: Logback and Log4j2 configs showing console/JSON/file output, a
  configurable log directory, and feature parity with the native logger.

Refs ArcadeData#4276
@mergify

mergify Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@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 introduces support for routing ArcadeDB's logs through the SLF4J facade, allowing embedding applications to receive engine logs in their own logging backend. It adds a new Slf4jLogger implementation, updates LogManager to support selecting the logger via a system property, and provides configuration examples for Logback and Log4j2. The review feedback suggests removing the local loggersCache in Slf4jLogger to prevent potential memory leaks and stale loggers during logging context reloads, delegating directly to LoggerFactory.getLogger instead.

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.

Comment thread engine/src/main/java/com/arcadedb/log/Slf4jLogger.java
Comment thread engine/src/main/java/com/arcadedb/log/Slf4jLogger.java Outdated
Comment thread engine/src/main/java/com/arcadedb/log/Slf4jLogger.java Outdated
@codacy-production

codacy-production Bot commented Jul 21, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 74 complexity

Metric Results
Complexity 74

View in Codacy

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.

Drop the local loggersCache: LoggerFactory.getLogger already caches
context-aware, and a local map risks stale loggers / a ClassLoader leak when
the logging context is reloaded (containers, Spring devtools, tests). Addresses
review feedback on ArcadeData#5361.
@ruispereira

Copy link
Copy Markdown
Contributor Author

Thanks @gemini-code-assist — good catch. Removed the local loggersCache in Slf4jLogger and now delegate directly to LoggerFactory.getLogger(name) (dropped the now-unused ConcurrentHashMap/ConcurrentMap imports too). Pushed.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Thank you for the update. The removal of the local cache and unused imports is a clean approach, as it correctly leverages the caching mechanism already provided by LoggerFactory.getLogger(name). I have no further comments on this implementation.

…og dir default

- Extract the 17-argument null check into anyNonNull() to lower the fixed-arity
  log() method's complexity (keeps the allocation-free path).
- Expand/clean javadoc on the new/changed methods (createLogger, slf4jLevel,
  anyNonNull, resolveLogger).
- examples/logging: default log directory now ./log, matching the native
  arcadedb.server.logsDirectory default (was ./logs).
…ndings

Fix a regression from moving slf4j-jdk14 to test scope: the runnable modules
(console, package) lost the SLF4J->JUL binding, so third-party libraries logging
via SLF4J (Undertow, Ratis, Gremlin, Micrometer) would go silent in the standalone
product. Re-declare slf4j-jdk14 at runtime scope in console and package, so the
distribution keeps routing those logs into java.util.logging while the library
modules (engine, network) still expose only slf4j-api (no binding leak).

Codacy:
- move LogManager.LOG_IMPL_PROPERTY to the top of the class (fields before methods);
- @SuppressWarnings("PMD.ExcessiveParameterList") on the 17-arg log() overload and
  anyNonNull() helper — the arity is mandated by the Logger interface's allocation-free path.
@ruispereira

Copy link
Copy Markdown
Contributor Author

@robfrank whenever you have a moment — this is ready for review. It implements the opt-in SLF4J-facade logging we discussed in #4276: default logger stays java.util.logging (no regression), -Darcadedb.log.impl=slf4j routes the engine's logs through the SLF4J facade so embedders get them in their own backend with no exclusions. The bot feedback (Gemini's local-cache note, Codacy) is already addressed, and the standalone distribution keeps its runtime binding. Happy to adjust anything — thanks!

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.69231% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.38%. Comparing base (e5700f3) to head (0163062).
⚠️ Report is 167 commits behind head on main.

Files with missing lines Patch % Lines
...ne/src/main/java/com/arcadedb/log/Slf4jLogger.java 78.15% 12 Missing and 14 partials ⚠️
...ine/src/main/java/com/arcadedb/log/LogManager.java 72.72% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #5361      +/-   ##
============================================
- Coverage     66.43%   65.38%   -1.06%     
- Complexity        0     1103    +1103     
============================================
  Files          1717     1736      +19     
  Lines        140939   144974    +4035     
  Branches      30174    31074     +900     
============================================
+ Hits          93636    94787    +1151     
- Misses        34630    37305    +2675     
- Partials      12673    12882     +209     

☔ View full report in Codecov by Harness.
📢 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.

@ruispereira

Copy link
Copy Markdown
Contributor Author

Heads-up on the red checks: they're all fork-PR permission/secret limitations, not test or build failures. Because this PR comes from a fork, the workflows run with a read-only GITHUB_TOKEN and no access to repo secrets, so the steps that write back to the PR or need a secret fail — while the actual work passes:

  • unit-testsRun Unit Tests with Coverage = success; only Unit Tests Reporter (publishes results, needs write) failed.
  • ha-integration-tests → same, the HA IT Tests Reporter step.
  • Check License Compliance → the check passed; only Comment on PR (if applicable) failed.
  • claude-reviewRun Claude Code Review needs the ANTHROPIC_API_KEY secret (unavailable to forks).
  • codecov/project & coverage-report → need a token to upload/post.

They should go green when run with the usual permissions on your side. Happy to rebase or adjust anything if it helps.

@lvca
lvca requested a review from robfrank July 21, 2026 14:10
@robfrank

Copy link
Copy Markdown
Collaborator

I'll check it next week, It looks fine from the surface I need to check deeply and I'm mostly off line right now.

@robfrank robfrank added this to the 26.8.1 milestone Jul 27, 2026
The parent POM moved slf4j-jdk14 to test scope, but server/pom.xml still
declared it at compile scope, so every application embedding arcadedb-server
kept inheriting the JUL binding and still needed <exclusions>. Drop both SLF4J
declarations from server: the slf4j-api facade already arrives from
arcadedb-engine, and the runnable artifacts (console, package) declare the
binding themselves at runtime scope. All four distribution flavours still
bundle slf4j-api and slf4j-jdk14 in lib/.

Also from review:

- Slf4jLogger now binds to SLF4J in its constructor, so a missing slf4j-api
  surfaces where LogManager can still fall back to DefaultLogger instead of
  escaping as a NoClassDefFoundError from the first log statement.
- LogManager reports an unrecognized arcadedb.log.impl value instead of
  silently behaving like the default, and createLogger() is package-private so
  the selection is testable.
- Slf4jLogger mirrors DefaultLogger's shutdown bypass: once the JVM is shutting
  down the backend may already have stopped its appenders, so INFO and above go
  to System.err.
- Document that the SLF4J logger never reads config/arcadedb-log.properties, so
  a packaged server selecting it loses the rolling file log unless JUL is
  pointed at that file explicitly.
- Fix the parent POM comment to point at console/package rather than server,
  sync the slf4j rows in ATTRIBUTIONS.md to the pinned 2.0.18, and replace
  fully-qualified names with imports.

Adds LogManagerLoggerSelectionTest (4 tests) and two shutdown-bypass tests;
50 logging tests green.
@robfrank

Copy link
Copy Markdown
Collaborator

Thanks @ruispereira, this is a well-built contribution: the opt-in design is genuinely non-disruptive, the Slf4jLogger semantics match DefaultLogger closely (level gating before formatting, printf substitution, best-effort on format mismatch), and the MDC save/restore correctly puts back exactly the keys it set. The example configs and the parity table are a nice touch.

I reviewed it and pushed a follow-up commit (0163062) directly to the branch rather than sending you round another cycle. Here is what it changes and why.

Two substantive fixes

1. The binding was still leaking from arcadedb-server.

The PR moves the parent's slf4j-jdk14 to test scope, but server/pom.xml still declared it with no scope:

com.arcadedb:arcadedb-server:jar
+- org.slf4j:slf4j-api:jar:2.0.18:compile
\- org.slf4j:slf4j-jdk14:jar:2.0.18:compile

So any application embedding arcadedb-server (the common case when you want the HTTP API) still inherited the JUL binding and still needed <exclusions>, which is exactly what #4276 is about. Only arcadedb-engine embedders got the clean tree.

I removed both SLF4J declarations from server: the slf4j-api facade already arrives transitively from arcadedb-engine, and git grep confirms Slf4jLogger.java is the only src/main file in the whole repo that references org.slf4j. The tree is now slf4j-api:compile + slf4j-jdk14:test, and your console/package runtime declarations become the load-bearing ones. Verified that all four distribution flavours still ship slf4j-api-2.0.18.jar and slf4j-jdk14-2.0.18.jar in lib/.

I also fixed the comment in the parent POM, which pointed at server/pom.xml rather than console/package.

2. The documented fallback in createLogger() never fired.

The javadoc claimed the catch (Throwable) covered "slf4j-api missing at runtime". It did not: Slf4jLogger touched LoggerFactory/MDC only in method bodies, so class loading and construction succeeded and the error escaped later. Running with engine/target/classes alone on the classpath:

step1: LogManager.instance() ...
step1 OK -> logger installed
step2: first log call ...
step2 THREW: java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory

No fallback message, so the catch block never ran, and the NoClassDefFoundError would surface from whatever engine code happened to log first. The constructor now calls LoggerFactory.getILoggerFactory(), which makes the probe real. Same run now prints the diagnostic, installs DefaultLogger, and logs normally. Mostly theoretical now that engine hard-depends on slf4j-api, but shaded or minimized deployments can strip it.

Smaller fixes

  • Silent fallback on a typo. -Darcadedb.log.impl=slf4 quietly gave you JUL with no diagnostic. It now reports the unrecognized value and lists the supported ones. createLogger() is package-private so the selection is testable.
  • No shutdown bypass. DefaultLogger has the shuttingDown -> System.err path from Irregular Null Pointer Exception on shut down #2813, but the SLF4J path had no equivalent, and Logback/Log4j2 register their own shutdown hooks that stop the context. write() now mirrors it for INFO and above.
  • Packaged-server gotcha. Slf4jLogger never calls DefaultLogger.init(), so config/arcadedb-log.properties is never read. Since the distribution bundles slf4j-jdk14, setting -Darcadedb.log.impl=slf4j on a packaged server still logs through JUL, but the FileHandler and formatter from that file are not installed, so log/arcadedb.log.* silently stops being written. Added a README section.
  • ATTRIBUTIONS.md slf4j rows synced to the pinned 2.0.18, and fully-qualified names replaced with imports per the project style.

Tests

Added LogManagerLoggerSelectionTest (4 tests covering unset / default / slf4j / unrecognized) and two shutdown-bypass tests. 50 logging tests green, and a full reactor mvn -DskipTests install passes, so the parent scope change breaks no module.

Not changed, for a maintainer decision

  • examples/logging/ placement. New top-level directory, outside the Maven reactor and not shipped in the distribution. docs/ or somewhere under package/ may be a better home. Happy to leave it where it is.
  • Your .gitignore note is correct and worth acting on. git check-ignore -v engine/src/main/java/com/arcadedb/log/Foo.java does report .gitignore:390 **/log, and the new test file needed git add -f. Fixing it properly means negating the directory itself (!**/src/main/java/**/log), which is a repo-wide rule change, so I would rather do that in its own PR than fold it in here.

Thanks again for the care that went into this one, particularly the parity table and the MDC restore semantics.

@robfrank
robfrank merged commit 1162781 into ArcadeData:main Jul 27, 2026
15 of 21 checks passed
robfrank added a commit that referenced this pull request Aug 14, 2026
…ing as default (#4276) (#5361)

Co-authored-by: robfrank <ro.franchini@gmail.com>
(cherry picked from commit 1162781)
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.

ArcadeDB writes logs directly to stdout instead of using SLF4J, producing inconsistent and unmanageable logs in host applications

2 participants