Skip to content

Repository files navigation

DX.Logger

License: MIT Delphi Versions Platforms

A minimalistic, cross-platform logging library for Delphi with a simple API and extensible provider architecture.

Features

  • Simple API: Just add DX.Logger to your uses clause and call DXLog()
  • Multiple Log Levels: Trace, Debug, Info, Warn, Error
  • Cross-Platform: Supports Windows, macOS, iOS, Android, and Linux
  • Platform-Specific Output:
    • Console applications: WriteLn
    • Windows: OutputDebugString
    • iOS/macOS: NSLog
    • Android: Android system log
    • Linux: syslog
  • Provider Architecture: Easily extend with custom log targets
  • Thread-Safe: Safe for use in multi-threaded applications
  • Thread-ID in every entry: Standard providers (File, UI, Seq, Default) render [Thread:N] next to the log level so parallel work is easy to follow
  • Optional Memory-Pressure Snippet: Opt-in via DX.Logger.SystemInfo — providers render a short [WS:45MB PB:22MB] block between thread-id and message; perfect for spotting memory growth in long-running services
  • Single-Unit Core: Minimal dependencies

Installation

Option 1: Manual Installation

  1. Clone or download this repository
  2. Add the source directory to your Delphi library path
  3. Add DX.Logger to your uses clause

Option 2: Git Submodule

git submodule add https://github.com/omonien/DX.Logger.git libs/DX.Logger

Then add libs/DX.Logger/source to your library path.

Quick Start

Basic Usage

uses
  DX.Logger;

begin
  DXLog('Hello World');                    // Info level
  DXLog('Debug message', TLogLevel.Debug); // Debug level
  DXLogError('Something went wrong!');     // Error level
end.

With File Logging

uses
  DX.Logger,
  DX.Logger.Provider.TextFile;  // Automatically adds file logging

begin
  // Optional: Configure file provider
  TFileLogProvider.SetLogFileName('myapp.log');
  TFileLogProvider.SetMaxFileSize(10 * 1024 * 1024); // 10 MB

  // Close the startup window so the file provider writes immediately
  // instead of buffering until StartupTimeoutMs (see "Startup & Configuration
  // Window" below).
  TDXLogger.CompleteConfiguration;

  DXLog('Application started');
  // ... your code
  DXLog('Application stopped');
end.

API Reference

Log Functions

// Generic log function with optional details
procedure DXLog(const AMessage: string; ALevel: TLogLevel = TLogLevel.Info; const ADetails: string = '');

// Convenience functions for specific levels
procedure DXLogTrace(const AMessage: string);
procedure DXLogDebug(const AMessage: string);
procedure DXLogInfo(const AMessage: string);
procedure DXLogWarn(const AMessage: string);
procedure DXLogError(const AMessage: string);

Details Parameter: The optional ADetails parameter allows you to provide additional context or supplementary information with your log entry. Each provider handles details according to its format and requirements (see Provider-Specific Details Handling below).

Log Levels

type
  TLogLevel = (
    Trace,   // Detailed diagnostic information
    Debug,   // Debugging information
    Info,    // General informational messages
    Warn,    // Warning messages
    Error    // Error messages
  );

Configuration

// Set minimum log level (messages below this level are ignored)
TDXLogger.SetMinLevel(TLogLevel.Info);

Provider-Specific Details Handling

The Details parameter in log functions provides additional contextual information. Each provider handles this data according to its format and purpose:

  • File Provider: Writes details as a separate TRACE-level line immediately after the main log entry, preserving all content
  • UI Provider: Writes details as a separate TRACE-level line, but truncates to 50 characters with a continuation message ("... [see log file for details]") to prevent UI overflow
  • Seq Provider: Includes details as a structured property in the CLEF (Compact Log Event Format) JSON payload, making it searchable and queryable in Seq

This design allows each provider to optimize details handling for its specific use case while maintaining a consistent API.

Startup & Configuration Window

TFileLogProvider registers itself in its unit's initialization section and becomes active immediately with default settings — before your DPR gets a chance to call SetLogFileName, SetMinLevel, etc. TSeqLogProvider and TUILogProvider do not self-register (registering them is left to the host application), but the same gap applies once they are: application-specific configuration can only run in the DPR body, potentially after entries could already be logged. To keep early entries from being written to the wrong target (or racing a not-yet-configured provider), TDXLogger opens a configuration window from process start:

Situation Default provider All other registered providers
Window open writes immediately (current MinLevel applies, as today) receive nothing; every entry is appended to the startup buffer (unfiltered, all levels)
Window closes unchanged buffered entries are replayed in original order, filtered with the MinLevel valid at close time; buffer is discarded afterwards
Window closed unchanged live dispatch, as today

Close the window explicitly by calling TDXLogger.CompleteConfiguration as the first statement(s) after begin, once all providers are configured:

program MyApp;

uses
  // Memory managers (FastMM etc.) first — they must not depend on DX.Logger.
  // Then the logger and ALL provider units, before anything else, so their
  // initialization runs as early as possible:
  DX.Logger,
  DX.Logger.Provider.TextFile,
  Vcl.Forms,
  { ... },
  Main.Form in 'Main.Form.pas';

begin
  // Configure providers first, then close the configuration window.
  // Without CompleteConfiguration the window auto-closes after
  // TDXLogger.StartupTimeoutMs (default: 10 s) or at process shutdown —
  // early entries are never lost either way, subject to the startup
  // buffer's 10 000-entry cap (oldest entries kept; newest dropped on
  // overflow, with the drop count reported at replay).
  TFileLogProvider.SetLogFileName('LOG\MyApp.log');
  TDXLogger.SetMinLevel(TLogLevel.Trace);
  TDXLogger.CompleteConfiguration;
  Application.Initialize;
  { ... }
end.

If your DPR never calls CompleteConfiguration explicitly — or a UI provider is only bound later (e.g. in FormCreate) — the window still closes automatically after TDXLogger.StartupTimeoutMs (default 10000 ms), or at the latest during process shutdown. Early entries are never lost either way, subject to the startup buffer's 10 000-entry cap (oldest entries kept; newest dropped on overflow, with the drop count reported at replay); unadapted existing applications just see non-default-provider output appear up to StartupTimeoutMs later than before.

See docs/CONFIGURATION.md for the full picture, including StartupTimeoutMs semantics, UI-provider timing, and notes for custom-provider authors.

Providers

File Provider

The file provider supports:

  • Automatic file creation
  • Configurable file name
  • Automatic file rotation based on size
  • Thread-safe file writing
  • UTF-8 encoding

Configuration:

uses
  DX.Logger,
  DX.Logger.Provider.TextFile;

// Set custom log file name
TFileLogProvider.SetLogFileName('C:\Logs\myapp.log');

// Set maximum file size before rotation (default: 10 MB)
TFileLogProvider.SetMaxFileSize(5 * 1024 * 1024); // 5 MB

// Register provider
TDXLogger.Instance.RegisterProvider(TFileLogProvider.Instance);

// Close the startup window so buffered entries are replayed immediately
// instead of after StartupTimeoutMs (see "Startup & Configuration Window" above).
TDXLogger.CompleteConfiguration;

When the log file reaches the maximum size, it's automatically renamed with a timestamp and a new file is created.

Seq Provider

The Seq provider sends structured log events to a Seq server using the CLEF (Compact Log Event Format).

Features:

  • Asynchronous, non-blocking logging
  • Automatic batching of events
  • Configurable batch size and flush interval
  • Thread-safe operation

Configuration:

uses
  DX.Logger,
  DX.Logger.Provider.Seq;

// Configure Seq server
TSeqLogProvider.SetServerUrl('https://your-seq-server.example.com');
TSeqLogProvider.SetApiKey('your-api-key-here');

// Optional: Configure batching
TSeqLogProvider.SetBatchSize(20);        // Default: 10
TSeqLogProvider.SetFlushInterval(5000);  // Default: 2000 ms

// Register provider
TDXLogger.Instance.RegisterProvider(TSeqLogProvider.Instance);

// Close the startup window so buffered entries are replayed immediately
// instead of after StartupTimeoutMs (see "Startup & Configuration Window" above).
TDXLogger.CompleteConfiguration;

// Use logging as normal
DXLog('Application started');

// Manually flush if needed
TSeqLogProvider.Instance.Flush;

Important: Never commit real API keys! See docs/CONFIGURATION.md for secure credential management.

See docs/SEQ_PROVIDER.md for detailed documentation.

UI Provider

The UI provider enables logging to visual controls like TMemo.Lines or any TStrings-based component.

Features:

  • Thread-safe UI updates via TThread.Synchronize
  • Automatic batching for better performance
  • Configurable insert position (top or bottom)
  • Details truncation to prevent UI overflow

Configuration:

uses
  DX.Logger,
  DX.Logger.Provider.UI;

// Register UI provider with TMemo.Lines
TUILogProvider.Instance.ExternalStrings := MemoInfo.Lines;
TUILogProvider.Instance.AppendOnTop := False;  // False = append at bottom (default)
TDXLogger.Instance.RegisterProvider(TUILogProvider.Instance);

// Binding here (e.g. FormCreate) relies on the startup window's fallback
// timer (StartupTimeoutMs, default 10 s) to replay buffered boot lines into
// the memo — or call TDXLogger.CompleteConfiguration explicitly once every
// provider is configured. See "Startup & Configuration Window" above and
// docs/CONFIGURATION.md#ui-providers for both patterns.

// Use logging as normal
DXLog('Application started');
DXLog('Processing item', TLogLevel.Info, 'Large JSON payload here...');

// Unregister when form closes
TUILogProvider.Instance.ExternalStrings := nil;

Details Handling: When log entries include details, the UI provider writes them as a separate TRACE-level line. To prevent UI overflow with large details (e.g., JSON payloads, stack traces), details are truncated to 50 characters with a continuation message: "... [see log file for details]". This keeps the UI readable while preserving full details in file logs.

Optional: Memory-Pressure in Log Entries

For long-running services it is often useful to see the current process memory right in the log stream without building a custom tool chain. DX.Logger.SystemInfo provides a cross-platform ready-to-use implementation that you can enable with a single line.

uses
  DX.Logger,
  DX.Logger.SystemInfo;

begin
  EnableMemoryInfo;              // default 500 ms cache
  // or: EnableMemoryInfo(1000); // custom cache interval in ms
  ...
end.

From that moment on every log entry gets a short memory snippet attached, and the standard providers render it between [Thread:N] and the message:

[2026-04-15 11:30:50.090] [INFO] [Thread:5944] [WS:37MB PB:16MB] Request received
  • WS = Working Set (resident memory)
  • PB = Private Bytes / virtual size

The snapshot is cached (default 500 ms) so high-frequency log calls stay cheap. The Seq provider exposes it as a structured MemoryInfo field (not inside @m) so it is queryable and chart-able.

Platform coverage

Platform Source
Windows GetProcessMemoryInfo (PSAPI)
macOS / iOS task_info(MACH_TASK_BASIC_INFO)
Linux / Android /proc/self/status (VmRSS, VmSize)
Other Returns empty snapshot; logging continues unaffected

TProcessMemoryMonitor.IsSupported tells you at runtime whether the current platform has a real implementation.

Under the hood

EnableMemoryInfo is a thin wrapper that installs a TDXLogger.MemoryInfoCallback pointing at TProcessMemoryMonitor.GetSnapshot.ToShortString. If you need full control (e.g. supply the snapshot from FastMM4 counters, mock it in tests, or add extra fields), assign your own callback directly:

TDXLogger.Instance.MemoryInfoCallback :=
  function: string
  begin
    Result := 'Heap:' + IntToStr(MyHeapBytes div 1048576) + 'MB';
  end;

DX.Logger itself has no dependency on any specific memory library — it just asks for a string. Call DisableMemoryInfo or assign nil to remove the callback again.

Creating Custom Providers

You can create custom log providers by implementing the ILogProvider interface:

type
  TMyCustomProvider = class(TInterfacedObject, ILogProvider)
  public
    procedure Log(const AEntry: TLogEntry);
  end;

procedure TMyCustomProvider.Log(const AEntry: TLogEntry);
begin
  // Your custom logging logic here. AEntry contains:
  //   Timestamp  : TDateTime
  //   Level      : TLogLevel
  //   Message    : string
  //   Details    : string   (optional, e.g. large JSON payload)
  //   ThreadID   : TThreadID
  //   MemoryInfo : string   (optional, set when a MemoryInfoCallback is installed)
end;

// Register your provider
TDXLogger.Instance.RegisterProvider(TMyCustomProvider.Create);

Platform-Specific Behavior

Windows

  • Console apps: Messages appear in console window
  • GUI apps: Messages sent to OutputDebugString (visible in DebugView or IDE)
  • File provider available

macOS

  • Uses NSLog for system logging
  • Messages appear in Console.app
  • File provider available

iOS

  • Uses NSLog for system logging
  • Messages appear in Xcode console
  • File provider available

Android

  • Uses Android system log (__android_log_write)
  • Messages visible via adb logcat
  • Tag: "DXLogger"
  • File provider available

Linux

  • Uses syslog for system logging
  • Messages appear in system logs
  • File provider available

Configuration & Security

For information on securely managing API keys and sensitive configuration:

Examples

Available Examples

Each example includes its own README with setup instructions.

Testing

The project includes comprehensive unit tests using DUnitX. See tests/README.md for details.

To run tests:

cd tests
dcc32 DX.Logger.Tests.dpr
DX.Logger.Tests.exe

Requirements

  • Delphi 10.3 or later (for inline variables)
  • Supported platforms: Windows, macOS, iOS, Android, Linux

Project Structure

DX.Logger/
├── source/
│   ├── DX.Logger.pas                     # Core logger unit
│   ├── DX.Logger.SystemInfo.pas          # Optional: CPU/memory snippet + static system config
│   ├── DX.Logger.ThreadCpu.pas           # Optional: per-thread CPU diagnostic (top-N + hot-thread IP)
│   ├── DX.Logger.Provider.TextFile.pas   # File logging provider
│   ├── DX.Logger.Provider.Seq.pas        # Seq logging provider
│   └── DX.Logger.Provider.UI.pas         # UI logging provider
├── examples/
│   ├── SimpleConsole/                    # Console example application
│   └── SeqExample/                       # Seq provider example
├── tests/
│   ├── DUnitX/                           # DUnitX framework (submodule)
│   ├── DX.Logger.Tests.dpr               # Test project
│   ├── DX.Logger.Tests.Core.pas          # Core logger tests
│   ├── DX.Logger.Tests.FileProvider.pas  # File provider tests
│   ├── DX.Logger.Tests.SeqProvider.pas   # Seq provider tests
│   └── README.md                         # Test documentation
├── docs/
│   ├── Delphi Style Guide EN.md          # Coding standards
│   ├── SEQ_PROVIDER.md                   # Seq provider documentation
│   └── CONFIGURATION.md                  # Configuration guide
├── config.example.ini                    # Example configuration (safe to commit)
└── README.md

Coding Standards

This project follows the Delphi Style Guide available in docs/Delphi Style Guide EN.md.

Key conventions:

  • Local variables: L prefix (e.g., LMessage)
  • Fields: F prefix (e.g., FProviders)
  • Parameters: A prefix (e.g., AMessage)
  • Constants: c prefix (e.g., cMaxSize)
  • 2 spaces indentation
  • UTF-8 with BOM encoding
  • CRLF line endings

License

MIT License

Copyright (c) 2025 Olaf Monien

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Contributing

Contributions are welcome! Please ensure:

  • Code follows the Delphi Style Guide
  • All files use UTF-8 with BOM encoding
  • Line endings are CRLF
  • Output paths follow the $(Platform)/$(Config) pattern
  • Add tests for new features
  • Update documentation as needed

How to Contribute

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Support

Changelog

See CHANGELOG.md for a list of changes in each version.

Acknowledgments

  • Built with Delphi
  • Testing framework: DUnitX
  • Inspired by modern logging libraries across various platforms

About

Minimalistic cross-platform logging library for Delphi with extensible provider architecture

Resources

Contributing

Security policy

Stars

28 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages