Repository navigation
ADR 0002
Status: angenommen
Herkunft: #22 (Befund und Entscheidung), #32 (Filter statt logPriorityRange), #69 (keine Methoden für Fehler und Exceptions), #84 (Enum der App hinter einem Protokoll, Priorität als Int-Rohwert), #73 (@LogLevels und @Emoji, Filter speichern ihre Stufen), #72 (Umsetzung)
Bis Version 2 war LogLevel ein Enum mit fest eingebauten Stufen. Die drei unteren Stufen hatten je
zwei Namen: .debug und .xcode, .verbose und .staging, .info und .production. Die Paare
hatten dasselbe Emoji und dieselbe Priorität, waren aber verschiedene Fälle und damit ungleich.
PrettyLogProxy.logX, logS und logP erzeugten trotzdem die klassischen Fälle. Die semantischen
kamen nur an, wenn jemand sie von Hand übergab. Ein Target, der über die Stufe verzweigte, musste
deshalb beide Namen behandeln. Die Apps des Betreuers benutzen beide Benennungen: die eigenen Apps
eher die klassische, die Apps mit Staging-Umgebung eher die semantische.
Wenn die App ihre Stufen selbst festlegt, muss das Paket zwei Dinge trotzdem sicherstellen: Keine
zwei Stufen einer App dürfen dieselbe Priorität haben, sonst ist offen, welche ein Filter meint.
Und Apps mit einem OSLog- oder Backend-Target verzweigen per switch über die Stufe. Eine neu
hinzugefügte Stufe soll dort nicht still in einem default landen.
-
Das Paket liefert keine Log-Level. Die App deklariert ihre Stufen als eigenes Enum mit dem Makro
@LogLevels, eine Zeile pro Stufe:@LogLevels nonisolated enum AppLogLevel: Int { @Emoji("🟤") case debug = 100 @Emoji("🔴") case error = 900 @Emoji("🟢") case info = 500 }
-
Der
Int-Rohwert ist die Priorität. Stufen sind über die Priorität geordnet, nie über die Reihenfolge der Fälle, damit die Fälle alphabetisch sortiert werden können. -
LogLevelist ein Protokoll:RawRepresentablemitInt,CaseIterable,Comparable,HashableundSendable, mit den Anforderungenemojiund_$logLevelsMacro.priorityund<liefert eine Protokoll-Erweiterung,priorityist keine Anforderung und kann deshalb nicht ersetzt werden. -
@LogLevelserzeugtemoji,allCases, die Markierung_$logLevelsMacround die Konformität. Es lehnt alles ab, was kein Enum mitInt-Rohwert ist, und jeden Fall ohne@Emojioder mit mehr als einer Stufe.@Emojierzeugt selbst nichts. Die erzeugten Mitglieder übernehmenpublicoderpackagevom Enum. Das Makro ist der einzige vorgesehene Weg: Eine von Hand geschriebene Konformität scheitert an der Markierung, deren TypLogLevelsMacroMarkernur das Makro sinnvoll befüllt. -
LogTargethat die primäre assoziierte StufeLevel. Die App führt ihre Targets als[any LogTarget<AppLogLevel>],PrettyLogProxy.logist generisch über die Stufe, und Swift leitet sie ausas:undto:ab. -
Targets filtern über
LogLevelPriorityFilter<Level>:.all,.none,.only(_:...),.from(_:),.until(_:)und.between(_:and:). Jeder Filter speichert die Stufen, die er annimmt, ausgewählt ausallCases..only(.info)nimmt genau.infoan, und zwei Filter sind gleich, wenn sie dieselben Stufen annehmen. In Debug-Builds prüft der Filter, dass keine zwei Stufen dieselbe Priorität haben. -
PrettyLogProxyhat keine Methoden, die an eine bestimmte Stufe gebunden sind. Die Stufe ist beilog(_:sensitiveMessages:joinedBy:as:category:to:)ein Pflichtparameter. Eigene Methoden für Fehler und Exceptions hat das Paket nicht, die baut die App mitsensitiveMessagesselbst. -
README und Demo-App zeigen als Vorschlag die Werte aus Version 2, mit den klassischen Namen als Hauptbeispiel und den semantischen als Alternative. Die höchste Stufe bekommt 1000, weil ein Rohwert ein Literal sein muss und
Int.maxkeines ist.
Welche Stufen eine App braucht und wie sie heißen, hängt von der App ab. Eine App ohne Staging-Umgebung kann mit "Staging" nichts anfangen, eine mit Staging-Umgebung will genau das sagen. Ein Paket, das eine Benennung vorgibt, zwingt einen Teil der Apps zum Umbenennen. Ein Paket, das beide anbietet, hat zwei Namen für dieselbe Stufe. Wenn die App die Stufen selbst festlegt, gibt es nur eine Schreibweise pro App. Das passt zu ADR 0001: Targets und Log-Funktionen legt die App ebenfalls selbst an.
Das Enum erfüllt beide Anforderungen aus dem Kontext. Swift lehnt zwei Fälle mit demselben Rohwert ab,
also kann eine App zwei Stufen keine gleiche Priorität geben, und ein switch über die Stufen ist
ohne default vollständig: Eine neue Stufe ohne Zuordnung kompiliert nicht. Ohne Makro bräuchte
jede Stufe zwei Stellen, den Fall mit Rohwert und einen Zweig im switch für das Emoji. Das Makro
bringt beides in eine Zeile und lässt die Prüfung der Prioritäten trotzdem beim Compiler.
- Breaking Change für jede App, die das Paket nutzt. Sie braucht ein Enum mit ihren Stufen, ihre
Wrapper rufen
PrettyLogProxy.log(..., as:)stattlogDund Co., und ihre Targets nennen den Typ ihrer Stufen inemitundlevelFilter. - Die Mindestversionen steigen auf iOS 16, Mac Catalyst 16, macOS 13 und tvOS 16, weil
any LogTarget<Level>Laufzeitunterstützung braucht, die es erst ab diesen Versionen gibt. watchOS bleibt bei 9. - Das Paket hängt von
swift-syntaxab, in Version 604. Eine App, deren andere Pakete eine andere Version verlangen, kann das Paket nicht auflösen. Xcode fragt beim ersten Bauen, ob die App dem Makro vertraut. - Ein Target, das nur Emoji und Priorität braucht, kann generisch bleiben und in Apps mit
verschiedenen Stufen laufen. Ein Target, das Stufen auf etwas anderes abbildet, etwa auf
OSLogType, ist an das Enum einer App gebunden. - Eine Bibliothek mit eigenem Stufen-Enum kann die Targets einer App mit anderem Enum nicht
übernehmen, weil
any LogTarget<A>undany LogTarget<B>verschiedene Typen sind. - Ein Typ, der die erzeugte Markierung von Hand abschreibt, kompiliert trotzdem. Das lässt sich
nicht verhindern. Gleiche Prioritäten in seinem
allCasesmeldet nur die Prüfung im Filter, und nur in Debug-Builds. - Eine Methode für Key Events mit eigener Signatur gibt es im Paket nicht. Ob eine App bei Key Events sensible Teile zulässt, entscheidet ihr eigener Wrapper.
-
Die semantischen Namen als Aliase der klassischen.
static var xcode: LogLevel { .debug }hätte für die meisten Aufrufer ohne Änderung kompiliert. Verworfen, weil ein Target, der beide Namen getrennt behandelt, danach still ein anderes Verhalten bekommen hätte. Außerdem gäbe es weiter zwei Namen für dieselbe Stufe. - Nur die semantischen Stufen behalten. Hätte die doppelte Schreibweise mit einem Compilerfehler statt still entfernt. Verworfen, weil damit allen Apps die semantische Benennung vorgegeben wäre, auch denen ohne Staging-Umgebung.
-
logX,logSundlogPerzeugen ihre eigenen Fälle. Verworfen, weil die doppelte Schreibweise bliebe und Targets weiter beide Namen behandeln müssten. -
LogLevelals Struktur aus Emoji und Priorität, die Stufen als Erweiterung der App. Eine Zeile pro Stufe ohne Makro und ohne Abhängigkeit. Verworfen, weil das Paket die Stufen einer App nie gesammelt sieht und deshalb zwei Stufen mit gleicher Priorität nicht verhindern kann, und weil einswitchüber Werte einendefaultbraucht. -
Ordnung über die Reihenfolge der Fälle. Ein Enum ohne Rohwert,
Comparablevom Compiler erzeugt. Verworfen, weil Fälle üblicherweise alphabetisch sortiert werden und das Sortieren die Bedeutung still geändert hätte. -
AnyLogTarget<Level>als Typ-Löschung. Hätte iOS 15 gehalten. Verworfen, weil jede App jedes Target darin hätte einpacken müssen. -
Text als Rohwert (
case debug = "🟤 100", ohne Makro). Braucht keine Abhängigkeit, kompiliert aber mit zwei Stufen gleicher Priorität, solange das Emoji verschieden ist, und mit Tippfehlern wie6OO. -
Makro ohne Rohwert (
@Level("🟤", priority: 100) case debug). Liest sich etwas besser, verlegt aber die Prüfung eindeutiger Prioritäten vom Compiler in eigenen Makro-Code.
Hier stehen Architekturentscheidungen im heute gültigen Stand. Vorgänge bleiben Issues.