Perf: reduce hot‑path overhead (logger autoclosure, zero‑copy BinaryProtocol.decode, prealloc encoders)

This commit is contained in:
jack
2025-09-04 23:17:00 +02:00
parent 5273f13512
commit a486644b7f
8 changed files with 182 additions and 184 deletions
+5 -5
View File
@@ -51,6 +51,8 @@ class NostrRelayManager: ObservableObject {
}
private var messageQueue: [PendingSend] = []
private let messageQueueLock = NSLock()
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
// Exponential backoff configuration
private let initialBackoffInterval: TimeInterval = TransportConfig.nostrRelayInitialBackoffSeconds
@@ -64,6 +66,8 @@ class NostrRelayManager: ObservableObject {
init() {
// Initialize with default relays
self.relays = Self.defaultRelays.map { Relay(url: $0) }
// Deterministic JSON shape for outbound requests
self.encoder.outputFormatting = .sortedKeys
}
/// Connect to all configured relays
@@ -170,8 +174,6 @@ class NostrRelayManager: ObservableObject {
let req = NostrRequest.subscribe(id: id, filters: [filter])
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .sortedKeys // For consistent output
let message = try encoder.encode(req)
guard let messageString = String(data: message, encoding: .utf8) else {
SecureLogger.log("❌ Failed to encode subscription request", category: SecureLogger.session, level: .error)
@@ -221,7 +223,7 @@ class NostrRelayManager: ObservableObject {
messageHandlers.removeValue(forKey: id)
let req = NostrRequest.close(id: id)
let message = try? JSONEncoder().encode(req)
let message = try? encoder.encode(req)
guard let messageData = message,
let messageString = String(data: messageData, encoding: .utf8) else { return }
@@ -394,8 +396,6 @@ class NostrRelayManager: ObservableObject {
let req = NostrRequest.event(event)
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .sortedKeys
let data = try encoder.encode(req)
let message = String(data: data, encoding: .utf8) ?? ""
+8 -9
View File
@@ -40,23 +40,23 @@ extension Data {
extension Data {
// MARK: Writing
mutating func appendUInt8(_ value: UInt8) {
@inlinable mutating func appendUInt8(_ value: UInt8) {
self.append(value)
}
mutating func appendUInt16(_ value: UInt16) {
@inlinable mutating func appendUInt16(_ value: UInt16) {
self.append(UInt8((value >> 8) & 0xFF))
self.append(UInt8(value & 0xFF))
}
mutating func appendUInt32(_ value: UInt32) {
@inlinable mutating func appendUInt32(_ value: UInt32) {
self.append(UInt8((value >> 24) & 0xFF))
self.append(UInt8((value >> 16) & 0xFF))
self.append(UInt8((value >> 8) & 0xFF))
self.append(UInt8(value & 0xFF))
}
mutating func appendUInt64(_ value: UInt64) {
@inlinable mutating func appendUInt64(_ value: UInt64) {
for i in (0..<8).reversed() {
self.append(UInt8((value >> (i * 8)) & 0xFF))
}
@@ -113,21 +113,21 @@ extension Data {
// MARK: Reading
func readUInt8(at offset: inout Int) -> UInt8? {
@inlinable func readUInt8(at offset: inout Int) -> UInt8? {
guard offset >= 0 && offset < self.count else { return nil }
let value = self[offset]
offset += 1
return value
}
func readUInt16(at offset: inout Int) -> UInt16? {
@inlinable func readUInt16(at offset: inout Int) -> UInt16? {
guard offset + 2 <= self.count else { return nil }
let value = UInt16(self[offset]) << 8 | UInt16(self[offset + 1])
offset += 2
return value
}
func readUInt32(at offset: inout Int) -> UInt32? {
@inlinable func readUInt32(at offset: inout Int) -> UInt32? {
guard offset + 4 <= self.count else { return nil }
let value = UInt32(self[offset]) << 24 |
UInt32(self[offset + 1]) << 16 |
@@ -137,7 +137,7 @@ extension Data {
return value
}
func readUInt64(at offset: inout Int) -> UInt64? {
@inlinable func readUInt64(at offset: inout Int) -> UInt64? {
guard offset + 8 <= self.count else { return nil }
var value: UInt64 = 0
for i in 0..<8 {
@@ -220,4 +220,3 @@ extension Data {
return data
}
}
+94 -119
View File
@@ -139,6 +139,11 @@ struct BinaryProtocol {
}
// Header
// Reserve capacity to reduce reallocations. Estimate base size conservatively.
// header(13) + sender(8) + opt recipient(8) + opt originalSize(2) + payload + opt signature(64) + up to 255 pad
let estimatedPayload = payload.count + (isCompressed ? 2 : 0)
let estimated = headerSize + senderIDSize + (packet.recipientID == nil ? 0 : recipientIDSize) + estimatedPayload + (packet.signature == nil ? 0 : signatureSize) + 255
data.reserveCapacity(estimated)
data.append(packet.version)
data.append(packet.type)
data.append(packet.ttl)
@@ -222,135 +227,105 @@ struct BinaryProtocol {
// Core decoding implementation used by decode(_:) with and without padding removal
private static func decodeCore(_ raw: Data) -> BitchatPacket? {
// Minimum size check: header + senderID
guard raw.count >= headerSize + senderIDSize else {
return nil
}
// Minimum size: header + senderID
guard raw.count >= headerSize + senderIDSize else { return nil }
// Convert to array for safer indexed access
let dataArray = Array(raw)
var offset = 0
// Header parsing with bounds checks
guard offset < dataArray.count else { return nil }
let version = dataArray[offset]; offset += 1
// Check if version is 1 (only supported version)
guard version == 1 else {
return nil
}
guard offset < dataArray.count else { return nil }
let type = dataArray[offset]; offset += 1
guard offset < dataArray.count else { return nil }
let ttl = dataArray[offset]; offset += 1
// Timestamp - need 8 bytes
guard offset + 8 <= dataArray.count else { return nil }
let timestampData = Data(dataArray[offset..<offset+8])
let timestamp = timestampData.reduce(0) { result, byte in
(result << 8) | UInt64(byte)
}
offset += 8
// Flags
guard offset < dataArray.count else { return nil }
let flags = dataArray[offset]; offset += 1
let hasRecipient = (flags & Flags.hasRecipient) != 0
let hasSignature = (flags & Flags.hasSignature) != 0
let isCompressed = (flags & Flags.isCompressed) != 0
// Payload length - need 2 bytes
guard offset + 2 <= dataArray.count else { return nil }
let payloadLengthData = Data(dataArray[offset..<offset+2])
let payloadLength = payloadLengthData.reduce(0) { result, byte in
(result << 8) | UInt16(byte)
}
offset += 2
// Validate payloadLength is reasonable (prevent integer overflow)
guard payloadLength <= 65535 else { return nil }
// SenderID - need 8 bytes
guard offset + senderIDSize <= dataArray.count else { return nil }
let senderID = Data(dataArray[offset..<offset+senderIDSize])
offset += senderIDSize
// RecipientID if present
var recipientID: Data?
if hasRecipient {
guard offset + recipientIDSize <= dataArray.count else { return nil }
recipientID = Data(dataArray[offset..<offset+recipientIDSize])
offset += recipientIDSize
}
// Payload handling with comprehensive bounds checking
let payload: Data
if isCompressed {
// Compressed payload needs at least 2 bytes for original size
guard Int(payloadLength) >= 2 else { return nil }
// Check we have enough data for the original size prefix
guard offset + 2 <= dataArray.count else { return nil }
let originalSizeData = Data(dataArray[offset..<offset+2])
let originalSize = Int(originalSizeData.reduce(0) { result, byte in
(result << 8) | UInt16(byte)
})
offset += 2
// Validate original size is reasonable
guard originalSize >= 0 && originalSize <= 1048576 else { return nil } // Max 1MB
// Check we have enough data for the compressed payload
let compressedPayloadSize = Int(payloadLength) - 2
guard compressedPayloadSize >= 0 && offset + compressedPayloadSize <= dataArray.count else {
return nil
return raw.withUnsafeBytes { (buf: UnsafeRawBufferPointer) -> BitchatPacket? in
guard let base = buf.baseAddress else { return nil }
var offset = 0
func require(_ n: Int) -> Bool { offset + n <= buf.count }
// Read single byte
func read8() -> UInt8? {
guard require(1) else { return nil }
let v = base.advanced(by: offset).assumingMemoryBound(to: UInt8.self).pointee
offset += 1
return v
}
// Read big-endian 16-bit
func read16() -> UInt16? {
guard require(2) else { return nil }
let p = base.advanced(by: offset).assumingMemoryBound(to: UInt8.self)
let v = (UInt16(p[0]) << 8) | UInt16(p[1])
offset += 2
return v
}
// Copy N bytes into Data
func readData(_ n: Int) -> Data? {
guard require(n) else { return nil }
let ptr = base.advanced(by: offset)
let d = Data(bytes: ptr, count: n)
offset += n
return d
}
let compressedPayload = Data(dataArray[offset..<offset+compressedPayloadSize])
offset += compressedPayloadSize
// Version
guard let version = read8(), version == 1 else { return nil }
guard let type = read8() else { return nil }
guard let ttl = read8() else { return nil }
// Decompress with error handling
guard let decompressedPayload = CompressionUtil.decompress(compressedPayload, originalSize: originalSize) else {
return nil
// Timestamp 8 bytes BE
guard require(8) else { return nil }
var ts: UInt64 = 0
for _ in 0..<8 {
guard let b = read8() else { return nil }
ts = (ts << 8) | UInt64(b)
}
// Verify decompressed size matches expected
guard decompressedPayload.count == originalSize else {
return nil
// Flags
guard let flags = read8() else { return nil }
let hasRecipient = (flags & Flags.hasRecipient) != 0
let hasSignature = (flags & Flags.hasSignature) != 0
let isCompressed = (flags & Flags.isCompressed) != 0
// Payload length
guard let payloadLen = read16(), payloadLen <= 65535 else { return nil }
// SenderID
guard let senderID = readData(senderIDSize) else { return nil }
// Recipient
var recipientID: Data? = nil
if hasRecipient {
recipientID = readData(recipientIDSize)
if recipientID == nil { return nil }
}
payload = decompressedPayload
} else {
// Uncompressed payload
guard Int(payloadLength) >= 0 && offset + Int(payloadLength) <= dataArray.count else {
return nil
// Payload
let payload: Data
if isCompressed {
// Need original size (2 bytes)
guard let origSize16 = read16() else { return nil }
let originalSize = Int(origSize16)
guard originalSize >= 0 && originalSize <= 1_048_576 else { return nil }
let compSize = Int(payloadLen) - 2
guard compSize >= 0, let compressed = readData(compSize) else { return nil }
guard let decompressed = CompressionUtil.decompress(compressed, originalSize: originalSize),
decompressed.count == originalSize else { return nil }
payload = decompressed
} else {
guard let p = readData(Int(payloadLen)) else { return nil }
payload = p
}
payload = Data(dataArray[offset..<offset+Int(payloadLength)])
offset += Int(payloadLength)
// Signature
var signature: Data? = nil
if hasSignature {
signature = readData(signatureSize)
if signature == nil { return nil }
}
guard offset <= buf.count else { return nil }
return BitchatPacket(
type: type,
senderID: senderID,
recipientID: recipientID,
timestamp: ts,
payload: payload,
signature: signature,
ttl: ttl
)
}
// Signature if present
var signature: Data?
if hasSignature {
guard offset + signatureSize <= dataArray.count else { return nil }
signature = Data(dataArray[offset..<offset+signatureSize])
offset += signatureSize
}
// Final validation: ensure we haven't gone past the end
guard offset <= dataArray.count else { return nil }
return BitchatPacket(
type: type,
senderID: senderID,
recipientID: recipientID,
timestamp: timestamp,
payload: payload,
signature: signature,
ttl: ttl
)
}
}
+3
View File
@@ -15,6 +15,8 @@ struct AnnouncementPacket {
func encode() -> Data? {
var data = Data()
// Reserve: TLVs for nickname (2 + n), noise key (2 + 32), signing key (2 + 32)
data.reserveCapacity(2 + min(nickname.count, 255) + 2 + noisePublicKey.count + 2 + signingPublicKey.count)
// TLV for nickname
guard let nicknameData = nickname.data(using: .utf8), nicknameData.count <= 255 else { return nil }
@@ -88,6 +90,7 @@ struct PrivateMessagePacket {
func encode() -> Data? {
var data = Data()
data.reserveCapacity(2 + min(messageID.count, 255) + 2 + min(content.count, 255))
// TLV for messageID
guard let messageIDData = messageID.data(using: .utf8), messageIDData.count <= 255 else { return nil }
+37 -30
View File
@@ -63,8 +63,9 @@ final class BLEService: NSObject {
private let messageDeduplicator = MessageDeduplicator()
// 5. Fragment Reassembly (necessary for messages > MTU)
private var incomingFragments: [String: [Int: Data]] = [:]
private var fragmentMetadata: [String: (type: UInt8, total: Int, timestamp: Date)] = [:]
private struct FragmentKey: Hashable { let sender: UInt64; let id: UInt64 }
private var incomingFragments: [FragmentKey: [Int: Data]] = [:]
private var fragmentMetadata: [FragmentKey: (type: UInt8, total: Int, timestamp: Date)] = [:]
// Backoff for peripherals that recently timed out connecting
private var recentConnectTimeouts: [String: Date] = [:] // Peripheral UUID -> last timeout
@@ -88,6 +89,7 @@ final class BLEService: NSObject {
var myPeerID: String = ""
var myNickname: String = "anon"
private let noiseService = NoiseEncryptionService()
private var myPeerIDData: Data = Data()
// MARK: - Advertising Privacy
// No Local Name by default for maximum privacy. No rotating alias.
@@ -331,6 +333,7 @@ final class BLEService: NSObject {
// Derive stable peer ID from Noise static public key fingerprint (first 8 bytes 16 hex chars)
let fingerprint = noiseService.getIdentityFingerprint() // 64 hex chars
self.myPeerID = String(fingerprint.prefix(16))
self.myPeerIDData = Data(hexString: myPeerID) ?? Data()
// Set queue key for identification
messageQueue.setSpecific(key: messageQueueKey, value: ())
@@ -486,7 +489,7 @@ final class BLEService: NSObject {
// Send leave message synchronously to ensure delivery
let leavePacket = BitchatPacket(
type: MessageType.leave.rawValue,
senderID: Data(hexString: myPeerID) ?? Data(),
senderID: myPeerIDData,
recipientID: nil,
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: Data(),
@@ -614,7 +617,7 @@ final class BLEService: NSObject {
let encrypted = try noiseService.encrypt(payload, for: peerID)
let packet = BitchatPacket(
type: MessageType.noiseEncrypted.rawValue,
senderID: Data(hexString: myPeerID) ?? Data(),
senderID: myPeerIDData,
recipientID: Data(hexString: peerID),
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: encrypted,
@@ -666,7 +669,7 @@ final class BLEService: NSObject {
let encrypted = try noiseService.encrypt(typedPayload, for: peerID)
let packet = BitchatPacket(
type: MessageType.noiseEncrypted.rawValue,
senderID: Data(hexString: myPeerID) ?? Data(),
senderID: myPeerIDData,
recipientID: Data(hexString: peerID),
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: encrypted,
@@ -823,7 +826,7 @@ final class BLEService: NSObject {
let packet = BitchatPacket(
type: MessageType.noiseEncrypted.rawValue,
senderID: Data(hexString: myPeerID) ?? Data(),
senderID: myPeerIDData,
recipientID: recipientData,
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: encrypted,
@@ -877,7 +880,7 @@ final class BLEService: NSObject {
// Send handshake init
let packet = BitchatPacket(
type: MessageType.noiseHandshake.rawValue,
senderID: Data(hexString: myPeerID) ?? Data(),
senderID: myPeerIDData,
recipientID: Data(hexString: peerID),
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: handshakeData,
@@ -927,7 +930,7 @@ final class BLEService: NSObject {
let packet = BitchatPacket(
type: MessageType.noiseEncrypted.rawValue,
senderID: Data(hexString: myPeerID) ?? Data(),
senderID: myPeerIDData,
recipientID: Data(hexString: peerID),
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: encrypted,
@@ -1270,8 +1273,11 @@ final class BLEService: NSObject {
// Minimum header: 8 bytes ID + 2 index + 2 total + 1 type
guard packet.payload.count >= 13 else { return }
let senderHex = packet.senderID.hexEncodedString()
let fragmentID = packet.payload[0..<8].map { String(format: "%02x", $0) }.joined()
// Compute compact fragment key (sender: 8 bytes, id: 8 bytes), big-endian
var senderU64: UInt64 = 0
for b in packet.senderID.prefix(8) { senderU64 = (senderU64 << 8) | UInt64(b) }
var fragU64: UInt64 = 0
for b in packet.payload.prefix(8) { fragU64 = (fragU64 << 8) | UInt64(b) }
// Parse big-endian UInt16 safely without alignment assumptions
let idxHi = UInt16(packet.payload[8])
let idxLo = UInt16(packet.payload[9])
@@ -1286,7 +1292,7 @@ final class BLEService: NSObject {
guard total > 0 && index >= 0 && index < total else { return }
// Store fragment
let key = "\(senderHex):\(fragmentID)"
let key = FragmentKey(sender: senderU64, id: fragU64)
if incomingFragments[key] == nil {
// Cap in-flight assemblies to prevent memory/battery blowups
if incomingFragments.count >= maxInFlightAssemblies {
@@ -1459,6 +1465,20 @@ final class BLEService: NSObject {
// Suppress announce logs to reduce noise
// Precompute signature verification outside barrier to reduce contention
let existingPeerForVerify = collectionsQueue.sync { peers[peerID] }
var verifiedAnnounce = false
if packet.signature != nil {
verifiedAnnounce = noiseService.verifyPacketSignature(packet, publicKey: announcement.signingPublicKey)
if !verifiedAnnounce {
SecureLogger.log("⚠️ Signature verification for announce failed \(peerID.prefix(8))", category: SecureLogger.security, level: .warning)
}
}
if let existingKey = existingPeerForVerify?.noisePublicKey, existingKey != announcement.noisePublicKey {
SecureLogger.log("⚠️ Announce key mismatch for \(peerID.prefix(8))… — keeping unverified", category: SecureLogger.security, level: .warning)
verifiedAnnounce = false
}
// Track if this is a new or reconnected peer
var isNewPeer = false
var isReconnectedPeer = false
@@ -1483,21 +1503,8 @@ final class BLEService: NSObject {
isNewPeer = (existingPeer == nil)
isReconnectedPeer = wasDisconnected
// Verify packet signature using the announced signing public key
var verified = false
if packet.signature != nil {
// Verify that the packet was signed by the signing private key corresponding to the announced signing public key
verified = noiseService.verifyPacketSignature(packet, publicKey: announcement.signingPublicKey)
if !verified {
SecureLogger.log("⚠️ Signature verification for announce failed \(peerID.prefix(8))", category: SecureLogger.security, level: .warning)
}
}
// If existing peer has a different noise public key, do not consider this verified
if let existing = existingPeer, let existingKey = existing.noisePublicKey, existingKey != announcement.noisePublicKey {
SecureLogger.log("⚠️ Announce key mismatch for \(peerID.prefix(8))… — keeping unverified", category: SecureLogger.security, level: .warning)
verified = false
}
// Use precomputed verification result
let verified = verifiedAnnounce
// Require verified announce; ignore otherwise (no backward compatibility)
if !verified {
@@ -1689,7 +1696,7 @@ final class BLEService: NSObject {
// Send response
let responsePacket = BitchatPacket(
type: MessageType.noiseHandshake.rawValue,
senderID: Data(hexString: myPeerID) ?? Data(),
senderID: myPeerIDData,
recipientID: Data(hexString: peerID),
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: response,
@@ -1845,7 +1852,7 @@ final class BLEService: NSObject {
// Create packet with signature using the noise private key
let packet = BitchatPacket(
type: MessageType.announce.rawValue,
senderID: Data(hexString: myPeerID) ?? Data(),
senderID: myPeerIDData,
recipientID: nil,
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: payload,
@@ -1879,7 +1886,7 @@ final class BLEService: NSObject {
let encrypted = try noiseService.encrypt(payload, for: peerID)
let packet = BitchatPacket(
type: MessageType.noiseEncrypted.rawValue,
senderID: Data(hexString: myPeerID) ?? Data(),
senderID: myPeerIDData,
recipientID: Data(hexString: peerID),
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: encrypted,
@@ -1916,7 +1923,7 @@ final class BLEService: NSObject {
let encrypted = try noiseService.encrypt(payload, for: peerID)
let packet = BitchatPacket(
type: MessageType.noiseEncrypted.rawValue,
senderID: Data(hexString: myPeerID) ?? Data(),
senderID: myPeerIDData,
recipientID: Data(hexString: peerID),
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: encrypted,
+4 -4
View File
@@ -13,7 +13,7 @@ struct CompressionUtil {
// Compression threshold - don't compress if data is smaller than this
static let compressionThreshold = TransportConfig.compressionThresholdBytes // bytes
// Compress data using zlib algorithm (most compatible)
// Compress data using LZFSE for speed on Apple platforms
static func compress(_ data: Data) -> Data? {
// Skip compression for small data
guard data.count >= compressionThreshold else { return nil }
@@ -27,7 +27,7 @@ struct CompressionUtil {
return compression_encode_buffer(
destinationBuffer, data.count,
sourcePtr, data.count,
nil, COMPRESSION_ZLIB
nil, COMPRESSION_LZFSE
)
}
@@ -36,7 +36,7 @@ struct CompressionUtil {
return Data(bytes: destinationBuffer, count: compressedSize)
}
// Decompress zlib compressed data
// Decompress LZFSE compressed data
static func decompress(_ compressedData: Data, originalSize: Int) -> Data? {
let destinationBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: originalSize)
defer { destinationBuffer.deallocate() }
@@ -46,7 +46,7 @@ struct CompressionUtil {
return compression_decode_buffer(
destinationBuffer, originalSize,
sourcePtr, compressedData.count,
nil, COMPRESSION_ZLIB
nil, COMPRESSION_LZFSE
)
}
+21 -7
View File
@@ -9,6 +9,7 @@ final class MessageDeduplicator {
}
private var entries: [Entry] = []
private var head: Int = 0
private var lookup = Set<String>()
private let lock = NSLock()
private let maxAge: TimeInterval = TransportConfig.messageDedupMaxAgeSeconds // 5 minutes
@@ -28,10 +29,18 @@ final class MessageDeduplicator {
entries.append(Entry(messageID: messageID, timestamp: Date()))
lookup.insert(messageID)
if entries.count > maxCount {
let toRemove = entries.prefix(100)
toRemove.forEach { lookup.remove($0.messageID) }
entries.removeFirst(100)
// Soft-cap and advance head by a chunk to avoid O(n) shifting
if (entries.count - head) > maxCount {
let removeCount = min(100, entries.count - head)
for i in head..<(head + removeCount) {
lookup.remove(entries[i].messageID)
}
head += removeCount
// Periodically compact to reclaim memory
if head > entries.count / 2 {
entries.removeFirst(head)
head = 0
}
}
return false
@@ -61,6 +70,7 @@ final class MessageDeduplicator {
defer { lock.unlock() }
entries.removeAll()
head = 0
lookup.removeAll()
}
@@ -78,9 +88,13 @@ final class MessageDeduplicator {
private func cleanupOldEntries() {
let cutoff = Date().addingTimeInterval(-maxAge)
while let first = entries.first, first.timestamp < cutoff {
lookup.remove(first.messageID)
entries.removeFirst()
while head < entries.count, entries[head].timestamp < cutoff {
lookup.remove(entries[head].messageID)
head += 1
}
if head > 0 && head > entries.count / 2 {
entries.removeFirst(head)
head = 0
}
}
}
+4 -4
View File
@@ -140,11 +140,11 @@ class SecureLogger {
}
/// Log general messages with automatic sensitive data filtering
static func log(_ message: String, category: OSLog = noise, level: LogLevel = .debug,
static func log(_ message: @autoclosure () -> String, category: OSLog = noise, level: LogLevel = .debug,
file: String = #file, line: Int = #line, function: String = #function) {
guard shouldLog(level) else { return }
let location = formatLocation(file: file, line: line, function: function)
let sanitized = sanitize("\(location) \(message)")
let sanitized = sanitize("\(location) \(message())")
#if DEBUG
os_log("%{public}@", log: category, type: level.osLogType, sanitized)
@@ -157,10 +157,10 @@ class SecureLogger {
}
/// Log errors with context
static func logError(_ error: Error, context: String, category: OSLog = noise,
static func logError(_ error: Error, context: @autoclosure () -> String, category: OSLog = noise,
file: String = #file, line: Int = #line, function: String = #function) {
let location = formatLocation(file: file, line: line, function: function)
let sanitized = sanitize(context)
let sanitized = sanitize(context())
let errorDesc = sanitize(error.localizedDescription)
#if DEBUG