Pure declarations unit covering the entire public surface: - TLogLevel (llTrace..llFatal) - TLogProc = procedure(Level: TLogLevel; const Category, Msg: string) of object - TLogProcPlain = plain-procedure variant - NullLog / TNullLogSink fallbacks (never guard on nil in consumer code) - LogLevelName / LogLevelChar utilities - Optional TConsoleLogger sink in log.console with min-level gate Zero dependencies outside FPC RTL. Adopted in-tree by fpc-binkp, fpc-comet, fpc-filexfer, and fpc-emsi; consumers supply one method bound as TLogProc to wire logging across every fpc-* lib from a single sink. Build green across x86_64-linux + all 5 i386 targets (linux, freebsd, win32, os2, go32v2). Tests in tests/test_log.pas.
fpc-log
A tiny Free Pascal library that provides the canonical
logger shape for the fpc-* ecosystem. Every other library —
fpc-msgbase,
fpc-ftn-transport,
fpc-binkp, and future siblings — depends on
this one for its log-callback signature. Consumers plug one
sink into all their libraries at once instead of wiring up
N different logger shapes.
Why a library
Each library needs to hand log messages to its consumer without imposing a concrete logging backend. Without a shared shape, every library invents its own callback type, and the consumer either writes N adapters or duplicates the sink logic N times.
This library is deliberately leaf-level — no dependency on any other fpc-* library. Pure callbacks and a level enum. Everything else (consoles, files, syslog, JSON sinks) is consumer-provided.
Scope
| Concern | Owned by |
|---|---|
| Log level enum + callback signature | fpc-log |
| Null sink (no-op default) | fpc-log |
| Console / file / syslog / JSON sinks | consumer |
| Log rotation, filtering, formatting | consumer |
| Concrete logger selection | consumer |
Status
Current: 0.1.0 — the shape is small and stable; consumers can pin this safely.
Usage (library author)
In any fpc-* library that needs to emit log messages, accept a
TLogProc (or ILogger interface, below) at construction and
call it:
uses log.types;
type
TMyLibThing = class
private
FLog: TLogProc;
public
constructor Create(ALog: TLogProc);
procedure DoWork;
end;
constructor TMyLibThing.Create(ALog: TLogProc);
begin
FLog := ALog;
if not Assigned(FLog) then
FLog := @NullLog; { default — discards everything }
end;
procedure TMyLibThing.DoWork;
begin
FLog(llInfo, 'mylib', 'starting work');
// ...
FLog(llDebug, 'mylib', 'work done');
end;
Usage (consumer)
Implement one method in your app that takes (Level, Category, Msg)
and pass it to every library:
procedure TMyApp.LogMessage(Level: TLogLevel;
const Category, Msg: string);
begin
WriteLn(Format('[%s] %s: %s', [
LogLevelName(Level), Category, Msg]));
end;
// ...
MsgBase := TMsgBase.Create(@Self.LogMessage);
Binkp := TBPSession.Create(..., @Self.LogMessage);
Bso := TBSOQueue.Create(..., @Self.LogMessage);
One sink, used everywhere. Filter by category to separate messages per library; by level to mute verbosity.
Layout
fpc-log/
├── src/
│ ├── log.version.pas — LOG_VERSION constant
│ ├── log.types.pas — TLogLevel, TLogProc, NullLog
│ └── log.console.pas — optional: TConsoleLogger helper
├── tests/
│ └── test_log.pas
├── README.md
├── CHANGELOG.md
├── build.sh
├── fpc.cfg
└── run_tests.sh
License
GPL-2.0 — same as the rest of the fpc-* ecosystem.