feat(core): ManaAppLog + appGroup/logSubsystem in ManaAppConfig (v1.7.0)

Audit 2026-05-17 V4. Ersetzt das hand-getippte Log.swift-Boilerplate
in jeder App durch einen Config-getriebenen Wrapper.

Neu:
- `ManaAppLog` — Factory fuer OSLog-Logger gegen ein ManaAppConfig.
  Standard-Kategorien app/auth/api/db/web, plus `category("…")` fuer
  app-spezifische Kategorien.
- `ManaAppConfig.appGroup: String?` (default nil) — Single-Source fuer
  den App-Group-String, der heute in jeder App 3-4× hardcoded steht.
- `ManaAppConfig.logSubsystem: String` (default = keychainService) —
  Subsystem fuer ManaAppLog.

Nichts breaking — beide neuen Felder haben Default-Implementations,
DefaultManaAppConfig.init hat zwei zusaetzliche optionale Parameter.

Tests: 4 neue ManaAppConfig-Tests + 5 neue ManaAppLog-Tests.
85/85 gruen (vorher 76/76).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-05-17 22:23:03 +02:00
parent 20c30fc321
commit 4ce22ac74e
5 changed files with 242 additions and 1 deletions

View file

@ -21,6 +21,31 @@ public protocol ManaAppConfig: Sendable {
/// Apple-Developer-Team-ID provisioniert sein und das Entitlement
/// `keychain-access-groups` mit demselben Wert tragen.
var keychainAccessGroup: String? { get }
/// App-Group für Daten-Sharing zwischen App Widget ShareExt.
/// Üblich `group.ev.mana.<app>`. `nil` für Apps ohne Extensions.
///
/// Single-Source für den App-Group-String, der heute in jeder App
/// 3-4× hardcoded steht (AppConfig + App-Entitlement + Widget-
/// Entitlement + ShareExt-Entitlement). Die Entitlements bleiben
/// hardcoded (das verlangt iOS), aber im Swift-Code ist der Wert
/// damit single-source.
var appGroup: String? { get }
/// OSLog-Subsystem für App-Logger, üblich `ev.mana.<app>`. Default
/// ist `keychainService` (der schon der Konvention folgt).
var logSubsystem: String { get }
}
// MARK: - Default-Implementationen
public extension ManaAppConfig {
/// Default `nil` Apps ohne Widget/ShareExt müssen nichts setzen.
var appGroup: String? { nil }
/// Default = `keychainService`. Beide folgen heute in allen Apps
/// derselben Konvention `ev.mana.<app>`.
var logSubsystem: String { keychainService }
}
/// Standard-Implementierung von ``ManaAppConfig``. Apps können diese
@ -29,14 +54,22 @@ public struct DefaultManaAppConfig: ManaAppConfig {
public let authBaseURL: URL
public let keychainService: String
public let keychainAccessGroup: String?
public let appGroup: String?
public let logSubsystem: String
public init(
authBaseURL: URL,
keychainService: String,
keychainAccessGroup: String? = nil
keychainAccessGroup: String? = nil,
appGroup: String? = nil,
logSubsystem: String? = nil
) {
self.authBaseURL = authBaseURL
self.keychainService = keychainService
self.keychainAccessGroup = keychainAccessGroup
self.appGroup = appGroup
// Konvention: log-Subsystem = keychainService, falls nicht
// explizit anders gewünscht.
self.logSubsystem = logSubsystem ?? keychainService
}
}

View file

@ -0,0 +1,67 @@
import Foundation
import OSLog
/// Convenience-Factory für OSLog-Logger gegen ein `ManaAppConfig`.
///
/// Hintergrund: Jede mana-App hatte vor v1.7.0 ein eigenes `Log.swift`
/// mit 4-6 hand-getippten `Logger(subsystem: "ev.mana.<slug>", )`-
/// Aufrufen der Subsystem-String und die Standard-Kategorien
/// (`app`/`auth`/`api`) wiederholten sich 8× identisch. `ManaAppLog`
/// kapselt den Subsystem-Lookup gegen die App-Config; Standard-
/// Kategorien sind Convenience-Accessoren, app-spezifische gehen über
/// ``ManaAppLog/category(_:)``.
///
/// **Verwendung** (in der App):
///
/// ```swift
/// import ManaCore
///
/// enum Log {
/// private static let mana = ManaAppLog(AppConfig.manaAppConfig)
/// static let app = mana.app
/// static let auth = mana.auth
/// static let api = mana.api
/// // App-spezifische Kategorien:
/// static let study = mana.category("study")
/// static let sync = mana.category("sync")
/// }
/// ```
///
/// Die Standard-Kategorien sind bewusst eine kleine Schublade
/// (app/auth/api/db/web). Cards/Memoro/Manaspur sollen ihre `study`-/
/// `audio`-/`tracking`-Kategorien weiterhin app-spezifisch deklarieren.
public struct ManaAppLog: Sendable {
public let subsystem: String
/// Direkter Constructor (für Tests oder andere Subsysteme).
public init(subsystem: String) {
self.subsystem = subsystem
}
/// Constructor aus einer ``ManaAppConfig``. Nutzt `logSubsystem`
/// (Default = `keychainService`, beides üblich `ev.mana.<app>`).
public init(_ config: ManaAppConfig) {
self.subsystem = config.logSubsystem
}
/// Allgemeiner App-Logger.
public var app: Logger { Logger(subsystem: subsystem, category: "app") }
/// Auth-bezogene Events.
public var auth: Logger { Logger(subsystem: subsystem, category: "auth") }
/// API-/Netzwerk-Calls.
public var api: Logger { Logger(subsystem: subsystem, category: "api") }
/// Datenbank-/SwiftData-/Persistenz-Events.
public var db: Logger { Logger(subsystem: subsystem, category: "db") }
/// Web-/WKWebView-bezogene Events (für Hybrid-Apps).
public var web: Logger { Logger(subsystem: subsystem, category: "web") }
/// App-spezifische Kategorie. Beliebige Strings, weil Console.app
/// Kategorien als Free-Text filtert.
public func category(_ name: String) -> Logger {
Logger(subsystem: subsystem, category: name)
}
}