Skip to content

V2: modernize, performance improments#89

Closed
andibacher wants to merge 60 commits into
MEONMedical:masterfrom
andibacher:experimental/v2-modernize-performance
Closed

V2: modernize, performance improments#89
andibacher wants to merge 60 commits into
MEONMedical:masterfrom
andibacher:experimental/v2-modernize-performance

Conversation

@andibacher

Copy link
Copy Markdown
Member

Added

  • JsonConfigurator for configuring logging from JSON files (log4qt.json).
    The JSON structure maps directly to flat property keys via dot-separated
    nesting. Automatic initialization searches for log4qt.json alongside
    log4qt.properties (.properties takes priority).
  • XmlConfigurator for configuring logging from XML files (log4qt.xml).
    Nested elements produce dot-separated keys; XML attributes are flattened
    as child properties. Automatic initialization searches for log4qt.xml
    after log4qt.json (.properties > .json > .xml priority).
  • Short factory aliases for all built-in components (e.g. Console,
    File, RollingFile, PatternLayout, LevelMatch) alongside the
    existing org.apache.log4j.* and Log4Qt::* names.
  • Dedicated PropertyConfigurator unit test suite (tests/propertytest).

Changed

  • Breaking: Minimum required Qt version raised to Qt 6.4. Qt 5 is no longer
    supported. Use branch 1.6 / release 1.6.x for Qt 5.7+ support.
  • Breaking: PropertyConfigurator now uses a Log4j2-style configuration
    format. The log4j. prefix is removed, appenders use explicit type keys,
    and loggers use appender references instead of inline declarations:
    • log4j.rootLogger=ALL, consolerootLogger.level=ALL +
      rootLogger.appenderRef.0.ref=console
    • log4j.appender.X=org.apache.log4j.ConsoleAppender
      appender.X.type=Console
    • log4j.appender.X.layout=org.apache.log4j.TTCCLayout
      appender.X.layout.type=TTCCLayout
    • log4j.logger.MyApp=ERROR, consolelogger.MyApp.name=MyApp +
      logger.MyApp.level=ERROR + logger.MyApp.appenderRef.0.ref=console
    • log4j.additivity.MyApp=falselogger.MyApp.additivity=false
    • Global settings: log4j.resetreset, log4j.Debug
      status, log4j.thresholdthreshold, etc.
  • Appender filters now configured via appender.X.filter.F.type=LevelMatch
    with properties under the same prefix.

Improvements

  • Performance optimizations
  • Modernize codebase to C++20 standard

Deprecated / Removed

  • Removed qmake support
  • Removed binary logger

andibacher and others added 30 commits February 14, 2026 17:54
- Update C++ standard from C++17 to C++20
- Fix thread-safety: replace incorrect volatile with std::atomic
- Modernize memory management: use std::unique_ptr instead of raw pointers
- Apply C++20 spaceship operator in Level class (6 operators → 1)
- Add [[nodiscard]], constexpr, and noexcept attributes
- Use strongly typed enums (enum : uint8_t)
- Remove C++17 conditional compilation blocks
- Fix naming convention: smart pointers use 'mp' prefix consistently
- Update compiler requirements (GCC 10+, Clang 10+, MSVC 19.29+)

BREAKING CHANGE: Requires C++20-capable compiler

Files changed: 17 core files
API: Backward compatible (internal changes only)
Modernize codebase to C++20

- Update C++ standard from C++17 to C++20
- Fix thread-safety: replace incorrect volatile with std::atomic
- Modernize memory management: use std::unique_ptr instead of raw pointers
- Apply C++20 spaceship operator in Level class (6 operators → 1)
- Add [[nodiscard]], constexpr, and noexcept attributes
- Use strongly typed enums (enum : uint8_t)
- Remove C++17 conditional compilation blocks
- Update compiler requirements (GCC 10+, Clang 10+, MSVC 19.29+)

BREAKING CHANGE: Requires C++20-capable compiler

Files changed: 15+ core files
API: Backward compatible (internal changes only)
Apply modern C++20 algorithms and eliminate legacy preprocessor conditionals.

Changes:
- Use std::find_if in appenderattachable.cpp (clearer intent)
- Eliminate temp container in dailyfileappender.cpp (memory savings)
- Add named predicates in hierarchy.cpp (better testability)
- Remove 13 C++17 conditional blocks (#if __cplusplus >= 201703L)
- Replace std::as_const with const auto& (simpler, equally safe)

Files changed: 9
Lines: +25, -40 (net -15)

Impact:
- Improved code clarity and maintainability
- Reduced memory allocations in file operations
- Removed all preprocessor conditionals for Qt container iteration
- No breaking changes (internal implementation only)

See ALGORITHMS_IMPLEMENTATION_COMPLETE.md for details.
Reserve 256 bytes in PatternFormatter::format() to avoid multiple reallocations during string building. Reduces allocations especially for complex pattern formatting.

Benchmark results:

- Very complex pattern (10k msgs): 4.0ms -> 3.5ms (+12.5% faster)

- Complex pattern (10k msgs): 3.9ms -> 4.0ms (neutral)

- Simple pattern (10k msgs): 4.0ms -> 4.1ms (neutral)

Modified: src/log4qt/helpers/patternformatter.cpp
Add move constructors and move assignment operators to LoggingEvent for better C++11/14 compliance and potential zero-copy optimization. Use append() instead of operator+= for consistency.

Changes:

- LoggingEvent: Add defaulted move constructor and move assignment

- LoggingEvent: Add explicit copy constructor/assignment (rule of five)

- LoggingEvent: Add rvalue reference constructors for zero-copy

- LoggingEvent: Use operator% for string concatenation

- WriterAppender: Use const reference to avoid QString copy

No measurable performance impact in benchmarks, but improves code quality and follows modern C++ best practices.

Modified:

- src/log4qt/loggingevent.h

- src/log4qt/loggingevent.cpp

- src/log4qt/writerappender.cpp
Replace QStringLiteral with modern u"..."_s operator for compile-time string literals.

Changes:

- Add Qt::StringLiterals namespace using directive for Qt 6.4+

- Add LOG4QT_LITERAL macro for version compatibility

- Replace 153+ QStringLiteral calls with u"..."_s syntax

Performance impact:

- Very complex pattern: 3.5ms -> 3.8ms (neutral, within variance)

- Overall timing: 6236ms -> 6112ms (2% faster total)

- All 25 tests passing

Benefits:

- Modern C++/Qt 6 best practices

- Better code readability

- Compile-time string construction

Modified: 50+ files in src/log4qt/
Implement TimestampProvider with configurable thread-local caching to reduce system call overhead.

Problem:

- Every LoggingEvent called QDateTime::currentMSecsSinceEpoch() (~500ns system call)

- At 10,000 msgs/sec = 5ms overhead in timestamp calls

- No caching mechanism existed

Solution:

- New TimestampProvider class with thread-local cache

- Default 1ms cache window (configurable 0-100ms)

- 99% reduction in system calls for high-frequency logging

- Thread-safe via thread_local (zero locks)

Performance gains:

- Simple logging (10k msgs): 4.1ms -> 4.0ms (2.4% faster)

- Overall test suite: 6112ms -> 5994ms (1.9% faster)

- High-frequency logging: 5% CPU savings at 100k msg/s

- All 25 tests passing

Benefits:

- Configurable precision vs performance trade-off

- Zero API changes or breaking changes

- Scales perfectly with thread count

Modified:

- src/log4qt/helpers/timestampprovider.h (new)

- src/log4qt/helpers/timestampprovider.cpp (new)

- src/log4qt/loggingevent.cpp

- src/log4qt/CMakeLists.txt
…mart pointers

P1 modernization changes:

1. Replace volatile with std::atomic (correctness fix):
   - fileappender.h: mAppendFile, mBufferedIo
   - binaryfileappender.h: mAppendFile, mBufferedIo
   - logger.h: mAdditivity
   - writerappender.h: mImmediateFlush
   - telnetappender.h: mImmediateFlush
   - consoleappender.h: mTarget
   - listappender.h: mConfiguratorList, mMaxCount
   - log4qt.h: update documentation comment

2. Replace raw new/delete with smart pointers (memory safety):
   - binaryfileappender: mFile, mDataStream -> std::unique_ptr
   - fileappender: mFile, mTextStream -> std::unique_ptr
   - consoleappender: mtextStream -> std::unique_ptr
   - configuratorhelper: mConfigurationFileWatch -> std::unique_ptr
   - systemlogappender: char* ident -> std::string
   - colorconsoleappender: new wchar_t[] -> std::vector<wchar_t>
   - patternformatter: QList<PatternConverter*> -> std::vector<std::unique_ptr<PatternConverter>>

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…used]], fold expressions

1. Replace #define color macros with constexpr (colorconsoleappender.cpp):
   - 21 NIX_* macros → constexpr int
   - 35 WIN_* macros → constexpr WORD (type-safe)

2. Replace typedef with using (modern type aliases):
   - factory.h: AppenderFactoryFunc, FilterFactoryFunc, LayoutFactoryFunc
   - configuratorhelper.h: ConfigureFunc

3. Add [[nodiscard]] to getter/query methods:
   - logger.h: additivity, level, name, effectiveLevel, isXxxEnabled, etc.
   - appender.h: filter, name, layout, requiresLayout
   - appenderskeleton.h: filter, layout, isActive, isClosed, name, threshold
   - layout.h: contentType, footer, header, name
   - loggingevent.h: all 17 getter methods
   - filter.h: next, decide
   - logmanager.h: 14 static getter/query methods

4. Replace Q_UNUSED() with [[maybe_unused]] (9 occurrences, 8 files):
   - binarylayout.cpp, configuratorhelper.cpp, logmanager.cpp (3×),
     patternformatter.cpp, wdcappender.cpp, denyallfilter.h, nullappender.cpp

5. Replace recursive variadic templates with C++17 fold expressions:
   - logger.h: 9 methods (debug, error, fatal, info, log, logWithLocation,
     trace, warn + MessageLogger::log)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nstexpr, concepts

1. Structured bindings with asKeyValueRange() (C++17):
   - logmanager.cpp: QHash iteration in environment settings trace logging
   - xmllayout.cpp: QHash iteration in XML properties output

2. Init-statements in if/switch (C++17):
   - propertyconfigurator.cpp: qobject_cast with immediate if-check
   - telnetappender.cpp: nextPendingConnection() with null check
   - systemlogappender.cpp: RegisterEventSource() with null check

3. constexpr expansion:
   - binaryloggingevent.cpp: static const char[] -> constexpr char[]
   - dailyfileappender.cpp: static const char[] -> constexpr char[]

4. C++20 concept for LogStream::operator<< (logstream.h):
   - QTextStreamable concept constrains template parameter
   - Guarded with #ifdef __cpp_concepts for C++17 compatibility

5. Cleaned up log4qtsharedptr.h documentation comment

Skipped (breaking API changes):
- std::optional for bool* ok parameters (P3.6)
- std::source_location for __FILE__/__LINE__ (P3.7)
- QStringView for const QString& parameters (P3.5)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add opt-in std::source_location constructors and overloads alongside
existing __FILE__/__LINE__/Q_FUNC_INFO-based API. All additions are
guarded by __cpp_lib_source_location feature-test macro for backward
compatibility with C++17 builds.

Changes:
- MessageContext: new constructor from std::source_location
- MessageLogger: new constructor taking std::source_location
- Logger::logWithLocation: new overload taking std::source_location
  with default parameter std::source_location::current()

Existing macros (l4qFatal, l4qError, etc.) remain unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Verifies that MessageContext constructed from std::source_location
correctly captures file name, line number, and function name, and
that these values propagate through LoggingEvent and PatternFormatter.
Test is guarded with #ifdef __cpp_lib_source_location and uses QSKIP
on compilers that lack support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace const QString & with QStringView where the parameter is only
inspected (compared, parsed) and never stored:

- Level::fromString(QStringView) - pure string comparisons
- LoggerRepository/Hierarchy::setThreshold(QStringView) - forwards to
  Level::fromString
- PatternFormatter::parseIntegeoption(QStringView) - isEmpty/toInt only

This avoids unnecessary QString copies when callers pass substrings
or string views.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ctices

This commit modernizes the entire CMake build system to follow modern
CMake best practices and conventions.

Key improvements:

* Update minimum CMake version from 3.10 to 3.16
* Add project metadata (description, homepage, language)
* Replace global add_definitions() with target-based compile definitions
* Replace global include_directories() with target-based includes
* Convert string-based compiler flags to list-based variables
* Add Log4Qt::log4qt ALIAS target for better namespacing
* Improve package configuration with configure_package_config_file()
* Add automatic Qt dependency finding via find_dependency()
* Modernize all if/endif syntax to lowercase without redundant arguments
* Improve code formatting and consistency across all CMake files
* Fix module include order (CMakePackageConfigHelpers before usage)

Benefits:
- Target-based build system prevents global pollution
- Better dependency management with automatic Qt finding
- Relocatable packages can be installed anywhere
- Backward compatible with existing projects
- Better IDE support and integration
- Cleaner, more maintainable code

Files modified:
- CMakeLists.txt: Main project configuration
- src/log4qt/CMakeLists.txt: Library target modernization
- cmake/Log4QtConfig.cmake.in: Package config improvements
- cmake/MacroEnsureOutOfSourceBuild.cmake: Syntax modernization
- cmake/cmake_uninstall.cmake.in: Formatting improvements
- CMAKE_MODERNIZATION_SUMMARY.md: Complete documentation of changes
- Remove legacy variable support (Log4Qt_LIBRARIES, Log4Qt_INCLUDE_DIRS)
- Enforce modern CMake target usage (Log4Qt::log4qt only)
- Update documentation to reflect clean modern interface
- Simplify Log4QtConfig.cmake.in by removing compatibility layer

BREAKING CHANGE: Projects using legacy variables must migrate to modern
imported targets. Replace target_link_libraries with Log4Qt::log4qt.
- Remove all .pro and .pri files (17 files total)
- Update Readme.md to document CMake as the only build system
- Update appveyor.yml CI to use CMake instead of qmake
- Project now exclusively uses CMake for building
- cache thread name in thread_local storage
- call QThread::currentThread() only once per event
…bug, info, warn, etc.) to check isEnabledFor() before performing expensive string interpolation.
LE_COPY_MOVE fallback macro for Qt < 5.13
- Replace Q_DISABLE_COPY with Q_DISABLE_COPY_MOVE in DatabaseAppender, DatabaseLayout, TelnetAppender
- Replace QLatin1String with u"..."_s string literals in hierarchy.cpp, properties.cpp
- Replace QScopedPointer with std::unique_ptr in TTCCLayout
- Use QString::fromLatin1() instead of QLatin1String(const char*) in ClassLogger

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ream, DailyFileAppender

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the poll-until-cached approach in setThreadNameToCurrent() with a
QPropertyNotifier that sets a dirty flag when the thread's objectName changes.
The cached name is re-read lazily on the next call, so thread renames are
picked up immediately. The hex pointer fallback is also cached per-thread.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The cache window setting is written by setCacheWindow() and read
concurrently from every logging thread. Using std::atomic with relaxed
ordering ensures data-race-free access without adding synchronization
overhead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
andibacher and others added 26 commits February 22, 2026 13:29
QEvent is not move-aware — the compiler-generated move ops fall back to
copying QEvent's internal state (including the posted flag), which can
cause double-deletion when the event dispatcher is involved. Removing
them lets the implicitly-deleted move ops fall back to the explicitly
defaulted copy operations, which is safe.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The notifier lambda captured a raw QThread* that could dangle if the
QThread object is destroyed before thread-local storage cleanup. Use
QPointer<QThread> so the callback safely no-ops when the QThread has
already been deleted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A race between checkEntryConditions (relaxed atomic reads of isActive/
isClosed) and closeInternal (which resets mpDispatcher under the lock)
could allow append() to dereference a null mpDispatcher. Guard the
postEvent call with a null check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The hex pointer fallback string was a static thread_local const,
initialized once and never updated. If the QThread wrapper object
changes (e.g. thread pool reuse), the cached address becomes stale.
Refresh it alongside cachedName whenever the nameChanged flag is set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MessageContext::file and ::function are raw const char* initialized to
nullptr by the default constructor. Appending nullptr to QString is
undefined behaviour. Add null checks for FILENAME_CONVERTER,
FUNCTIONNAME_CONVERTER, and LOCATION_CONVERTER.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The actual storage is file-scope statics in the .cpp file. The
commented-out member declarations were leftover from a draft and
misrepresent the class interface.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
If currentMSecsSinceEpoch() is called from another translation unit's
static initializer, the file-scope s_elapsedTimer may not have been
started yet. Add an isValid() check to lazily start it on first use.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the log level is disabled, no internal stream is allocated and all
data streamed via operator<< is silently discarded. Document this on
the constructor so maintainers understand the intentional behaviour.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove duplicated find_package and CMAKE_RUNTIME_OUTPUT_DIRECTORY calls
that were left over from concatenating two separate CMakeLists.txt
files. Both targets now share a single find_package and output dir.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A negative elapsedSinceLastUpdate (e.g. from timer restart) would
prevent the cache from ever refreshing. Treat it as expired alongside
the normal window check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mLevel is read from isEnabledFor/effectiveLevel on logging threads and
written by setLevel during configuration. Make it std::atomic<Level>
consistent with mAdditivity to eliminate the data race.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move all data members into a QSharedData-derived inner struct and store
them via QSharedDataPointer. This enables copy-on-write semantics so
that copying LoggingEvent (e.g. when posting across threads or storing
in lists) only bumps a reference count instead of deep-copying all
QString and QHash members. Add benchmark tests comparing the new
implicitly shared implementation against the old direct-member layout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a JsonConfigurator class that reads JSON configuration files and
flattens them into Properties for delegation to PropertyConfigurator.
This enables `log4qt.json` as an alternative to `log4qt.properties`
with zero duplication of parsing logic.

Key features:
- Nested JSON objects produce dot-separated property keys
- @Class key maps to the parent object's class name
- Booleans/numbers are stringified, variable substitution works
- LogManager auto-discovery searches .json after .properties at each
  location for backward compatibility
- File watching via configureAndWatch() is supported

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add XmlConfigurator that reads .xml config files, flattens them into
Properties (nested elements become dot-separated keys, 'class' attribute
sets the element's class name), and delegates to PropertyConfigurator.
LogManager auto-initialization now searches for log4qt.xml after
log4qt.json (.properties > .json > .xml priority).

Includes real-world test cases for both JSON and XML configurators
based on a production config with ConsoleAppender, DailyFileAppender,
TTCCLayout with custom date format, and variable substitution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the legacy Log4j1-style property format (log4j.appender.X=ClassName,
log4j.rootLogger=LEVEL, appenders) with a modern Log4j2-style structured
format using explicit type keys and appender references:

- PropertyConfigurator: new keys like appender.X.type, rootLogger.level,
  rootLogger.appenderRef.N.ref, logger.X.name/level/additivity
- Global settings: reset, status, threshold, handleQtMessages (no log4j. prefix)
- Factory: add short aliases (Console, File, PatternLayout, etc.)
- JsonConfigurator: simple flattening without @Class handling
- XmlConfigurator: skip root element name, flatten children directly,
  support attributes as child properties
- Update all tests (JSON, XML, property configurator) for new format
- Add dedicated PropertyConfigurator unit test suite (tests/propertytest)
- Update example .properties file

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Configuration.md documenting the Log4j2-style configuration format
for all three file types (Properties, JSON, XML) with complete examples,
reference tables for all built-in components, and usage instructions.

Update ChangeLog.md with detailed v2.0.0 breaking changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Handle "INHERITED" level string explicitly in configureLoggers by
  setting Level::NULL_INT, since OptionConverter::toLevel does not
  recognize it and would fall back to DEBUG.
- Only call removeAllAppenders() on root logger and named loggers
  when appenderRef keys are actually present, preventing unintended
  wipe of existing appenders during incremental reconfiguration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ngs docs

- Warn when a filter is configured for a non-AppenderSkeleton appender
- Rewrite XML testFlattenAttributes to actually use XML attributes
- Clarify Log4j2-style flat key format in QSettings documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CMakeLists.txt: replace Qt5/Qt6 dual detection with Qt6-only find_package,
  set minimum version to 6.4.0, update QT_DISABLE_DEPRECATED_BEFORE to 0x060400
- Readme.md: update minimum Qt version requirement to Qt 6.4
- ChangeLog.md: document breaking change in v2.0.0
- .github/copilot-instructions.md: add Copilot instructions for the repository

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts:
#	src/log4qt/binaryfileappender.cpp
#	src/log4qt/binaryfileappender.h
#	src/log4qt/binarylayout.cpp
#	src/log4qt/binarylayout.h
#	src/log4qt/binarylogger.cpp
#	src/log4qt/binarylogger.h
#	src/log4qt/binaryloggingevent.cpp
#	src/log4qt/binaryloggingevent.h
#	src/log4qt/binarylogstream.cpp
#	src/log4qt/binarylogstream.h
#	src/log4qt/binarytotextlayout.cpp
#	src/log4qt/binarytotextlayout.h
#	src/log4qt/binarywriterappender.cpp
#	src/log4qt/binarywriterappender.h
#	src/log4qt/helpers/binaryclasslogger.cpp
#	src/log4qt/helpers/binaryclasslogger.h
#	src/log4qt/rollingbinaryfileappender.cpp
#	src/log4qt/rollingbinaryfileappender.h
#	src/log4qt/varia/binaryeventfilter.cpp
#	src/log4qt/varia/binaryeventfilter.h
@andibacher
andibacher marked this pull request as draft March 24, 2026 09:59
andibacher and others added 2 commits March 24, 2026 13:08
Detect log4j. prefix keys and translate to Log4j2 format before parsing.
Supports class-as-value appenders/layouts/filters, inline comma-separated
root/logger values, category/rootCategory aliases, and additivity mapping.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@andibacher andibacher closed this Mar 24, 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.

1 participant