Repository navigation
Prepare 4.0.0: CI, Linux build, crash-safe file sink, metadata, race fixes - #10
Merged
Merged
Conversation
…lease Mirrors Perfect-CRUD's workflow minus the iOS job (the package declares macOS only). Warnings are errors; release builds enable testing for @testable imports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The handlers implement LogHandler.log(event:), and LogEvent only exists from swift-log 1.11.0 (LogEvent.error from 1.12.0). With the old from: "1.5.0" floor a consumer resolving swift-log 1.6-1.10 failed to build with "cannot find type 'LogEvent' in scope". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d races
Linux
- RemoteLogHandler imports FoundationNetworking when available; main did
not build on Linux ("cannot find 'URLRequest' in scope").
FileLogHandler
- FileSink opens the file with O_APPEND|O_CREAT|O_CLOEXEC and writes each
line with write(2), retrying EINTR and dropping the line on any other
error. The legacy seekToEndOfFile()/write(Data) trap on I/O errors
(on Linux: "'try!' expression unexpectedly raised ... No space left on
device" when writing to /dev/full). A path that cannot be opened prints
one warning on stderr instead of failing silently.
- The timestamp formatter is pinned to en_US_POSIX and the Gregorian
calendar (a th_TH user got year 2544, ar_SA got Arabic-Indic digits)
and is guarded by a lock.
- Metadata from the handler, its metadataProvider, the call and the
call's error are merged as swift-log's StreamLogHandler does. New
LogOptions.metadata (in .default) appends keys other than eventid after
the message as sorted key=value pairs; lines with only an eventid are
byte-for-byte unchanged, and .none still writes the bare message.
- LogFile.terminal tags its event priority=terminal so the file shows
[EMERG] as 3.x did, instead of [CRITICAL].
RemoteLogHandler
- Events go through one bounded queue per collector URL (1000 pending,
then dropped) drained by a single task, instead of an unbounded
detached Task and URLSession call per log line. Requests time out
after 10 s.
- The token is percent-encoded into the path; an empty server/token or a
non-http(s) URL disables the handler with one stderr warning.
- Merges metadataProvider and call metadata; sends extra keys as an
optional "metadata" object. The JSON keys are otherwise unchanged.
LogFile / bootstrap
- LogFile.logger is lock-protected instead of nonisolated(unsafe) (TSan
reported a data race when swapping it while logging).
- bootstrap rebinds LogFile's default logger, so LogFile calls made
before bootstrapping no longer pin it to swift-log's default backend.
- bootstrap gains a metadataProvider: parameter (default nil) and warns
instead of silently dropping the remote handler when remoteToken is
missing.
- LogFile.debug no longer evaluates its autoclosure when filtered.
All public API changes are additive (new defaulted parameters,
LogOptions.metadata, RemoteLogHandler.maxPendingEvents).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion Install from: "4.0.0"; Swift 6.2+, macOS 12+ and Linux; swift-log 1.12.0+. Documents the metadata suffix, POSIX timestamps, append-mode writes, the bounded remote queue and its JSON body, the stderr warnings for bad file/remote configuration, and how 3.x call sites map to 4.0 (including the changed remote JSON body). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…File rebind and re-entrancy
Remote
- Each POST is raced against a timer and abandoned (its task cancelled)
after 10 s whatever the transport does. URLRequest.timeoutInterval is an
idle timeout and Linux's URLSession.shared ignored it, so a collector
that trickled one byte every 3 s stalled the queue forever. The default
transport also uses an ephemeral session with 10 s request and
resource timeouts.
- Collector base URLs with a query or fragment are rejected.
File sink
- stat() the path first: directories, sockets and block devices are
refused with one warning. The file is opened with O_NONBLOCK (cleared
afterwards), so a FIFO with no reader is refused (ENXIO) instead of
hanging the first log call.
- Writing to a pipe whose reader left gave SIGPIPE (exit 141). Darwin
now sets F_SETNOSIGPIPE; Linux blocks SIGPIPE on the writing thread for
non-regular files and consumes the pending signal, so the write fails
with EPIPE.
- After a partial write the sink tries to write "\n" so the next line
doesn't glue onto the broken one.
Metadata
- Keys and values in the file suffix are escaped (\\ " \n \r \t, other
controls as \u{XX}) and quoted when empty or containing space, '=' or
'"', so a value can no longer forge a log line. Nested dictionaries
are rendered with sorted keys (also in the remote JSON).
- The terminal marker key is now "perfect.priority", so user "priority"
metadata can't turn a line into [EMERG].
LogFile
- The getter builds the default Logger outside the lock and publishes it
only if nothing replaced it meanwhile; a LoggingSystem factory that
itself uses LogFile deadlocked. A re-entrant build on the same thread
gets a no-op logger instead of recursing.
- bootstrap rebuilds an assigned logger that still has the default label
(what `LogFile.logger.logLevel = x` before bootstrap leaves) on the new
backend, keeping logLevel and handler metadata; before, it stayed on
the stderr handler forever. Other assigned loggers are kept.
Tests: line-format tests use /dev/null instead of leaving temp files;
the bounded-queue test is deterministic (gate-blocked transport); new
tests for the hard timeout, cancellation, FIFO, broken pipe, directory,
escaping, nested sorting, marker collision, URL rejection and rebind.
CI: drop the Swift 6.2 miscompile comment copied from Perfect-CRUD.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Xcode 26.3 (Swift 6.2.4) failed the test build with "unable to type-check this expression in reasonable time" on the bounded-queue test's array comparison. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…trap rebindDefaultLogger returned early when no logger was stored, without bumping the generation. A first LogFile use that was still building its logger on the pre-bootstrap backend then published it after bootstrap, and LogFile stayed on that backend (reviewer's rebind-race: 34/600 runs pinned on macOS, 180/400 on Linux 6.4, 251/400 on 6.2; now 0). The rebind now invalidates in-flight builds in that case too. The getter's body is factored into resolveLogger(make:) so a test can rebind mid-build deterministically (it fails without the fix). Document that the re-entrancy guard is same-thread only (a factory must not block on another thread that uses LogFile), that LogFile calls made from inside the factory are dropped, and that a path that can't be opened when LogFile's logger is created, such as a FIFO with no reader, stays disabled for that logger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Prepares Perfect-Logger for 4.0.0, the first release of the Swift 6 rewrite on apple/swift-log (3.3.0 is the 2018 code).
LogEvent(1.11) andLogEvent.error(1.12);from: "1.5.0"could not build.RemoteLogHandlernow importsFoundationNetworking.O_APPEND|O_CREAT|O_CLOEXEC, onewrite(2)per line; I/O errors (e.g. a full disk) drop the line instead of crashing. FIFOs open non-blocking (no hang without a reader), directories/sockets/block devices are refused, SIGPIPE is suppressed.en_US_POSIX+ Gregorian, so output no longer depends on the host locale.metadataProvider, call and error metadata are merged; non-eventidkeys are appended as escaped, sortedkey=valuepairs (newLogOptions.metadata, in.default). Lines carrying onlyeventidare unchanged.LogFile.loggeris lock-protected;bootstraprebinds the default logger (including one used or tuned before bootstrap) without a stale-logger race; a backend factory that logs no longer deadlocks.LogFile.terminalwrites[EMERG]again;LogFile.debug's autoclosure is lazy.The public API is source-compatible with
main; changes are additive (metadataProvider:parameters,LogOptions.metadata,RemoteLogHandler.maxPendingEvents). Behaviour changes for the release notes:LogOptions.default.rawValueis now 15, and the remote JSON body differs from 3.x (already the case onmain).Verification
swift:6.2-nobleandswift:6.4-nobleon arm64, andswift:6.4-nobleamd64, debug and release.🤖 Generated with Claude Code