Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2700"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "LogBackends"
BuildableName = "LogBackends"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
queueDebuggingEnabled = "No">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "LogBackends"
BuildableName = "LogBackends"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
19 changes: 18 additions & 1 deletion mobile/TeleportKit/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import PackageDescription

let package = Package(
name: "TeleportKit",
platforms: [.iOS(.v26)],
platforms: [.iOS(.v26), .macOS(.v26)],
products: [
.library(name: "SystemClients", targets: ["SystemClients"]),
.library(name: "LogBackends", targets: ["LogBackends"]),
Expand All @@ -13,6 +13,7 @@ let package = Package(
.package(url: "https://github.com/pointfreeco/swift-dependencies", .upToNextMajor(from: "1.14.0")),
.package(url: "https://github.com/pointfreeco/swift-sharing", .upToNextMajor(from: "2.9.1")),
.package(url: "https://github.com/apple/swift-log", .upToNextMajor(from: "1.14.0")),
.package(url: "https://github.com/apple/swift-collections", .upToNextMajor(from: "1.6.0")),
],
targets: [
.target(
Expand All @@ -26,7 +27,19 @@ let package = Package(
.target(
name: "LogBackends",
dependencies: [
.collections,
.logging,
.dependencies,
"SystemClients",
],
),
.testTarget(
name: "LogBackendsTests",
dependencies: [
.dependencies,
.logging,
"LogBackends",
"SystemClients",
],
),
],
Expand All @@ -52,6 +65,10 @@ extension Target.Dependency {
name: "Logging",
package: "swift-log",
)
fileprivate static let collections: Self = .product(
name: "Collections",
package: "swift-collections",
)
}

// MARK: - Build Settings
Expand Down
12 changes: 6 additions & 6 deletions mobile/TeleportKit/Sources/LogBackends/ConsoleLogHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see http://www.gnu.org/licenses/

import Dependencies
import Foundation
public import Logging

/// A simple print-based log handler primarily used for live debugging sessions.
public struct ConsoleLogHandler: LogHandler {
public let label: String

@Dependency(\.date.now)
private var now

private let timestampFormmater = Date.ISO8601FormatStyle(
dateSeparator: .dash,
dateTimeSeparator: .space,
Expand All @@ -46,11 +50,7 @@ public struct ConsoleLogHandler: LogHandler {
}

public func log(event: LogEvent) {
let timestamp = timestampFormmater.format(.now)
let metadataToLog = metadata.merging(event.metadata ?? [:]) { _, new in new }

let output = "\(timestamp) \(event.level.formatted) \(event.file):\(event.line) | \(event.message) \(metadataToLog.formatted)"

print(output)
let logMessage = LogFormatter.format(label: label, event: event, handlerMetadata: metadata, timestamp: now)
print(logMessage)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see http://www.gnu.org/licenses/

import Foundation
import Logging

/// LogFormatter doesn't currently maintain any local state, so it's simplest to implement it as an enum with static
/// functions.
enum LogFormatter {
private static let timestampFormatter = Date.ISO8601FormatStyle(
dateSeparator: .dash,
dateTimeSeparator: .space,
timeSeparator: .colon,
timeZoneSeparator: .colon,
includingFractionalSeconds: true,
timeZone: .autoupdatingCurrent,
)

/// Formats the log message
/// - Parameters:
/// - label: The label of the logger
/// - event: The event to log
/// - handlerMetadata: The metadata for the `LogHandler` (note: events carry their own metadata and this formatter
/// automatically coalesces them with the `LogHandler` metadata)
/// - timestamp: The time at which the logging event took place
/// - Returns: A formatted log message suitable for output to a debug console or writing to a file
static func format(
label: String,
event: LogEvent,
handlerMetadata: Logger.Metadata,
timestamp: Date,
) -> String {
let metadata = handlerMetadata.merging(event.metadata ?? [:]) { _, eventValue in eventValue }
var components: [String] = [
timestampFormatter.format(timestamp),
event.level.formatted,
"[\(label)]",
"\(event.file):\(event.line)",
"|",
"\(event.message)",
]

let formattedMetadata = metadata.formatted
if !formattedMetadata.isEmpty {
components.append(formattedMetadata)
}

if let error = event.error {
components.append("❌ error=\(error)")
}

return components.joined(separator: " ")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ extension Logger.Metadata {
var formatted: String {
guard !isEmpty else { return "" }
let metadataAsStrings = map { key, value in
let prefix = if key.lowercased().localizedStandardContains("error") {
"❌"
} else {
"🔸"
}
return "\(prefix) \(key)=\(value)"
"🔸 \(key)=\(value)"
}
return metadataAsStrings.joined(separator: " ")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see http://www.gnu.org/licenses/

import Dependencies
import Foundation
import Logging

/// A log handler that writes to a rotating file on disk.
public struct RotatingFileLogHandler: LogHandler {
public let label: String

@Dependency(\.date.now)
private var now

private let writer: RotatingFileWriter

public init(label: String, writer: RotatingFileWriter) {
self.label = label
self.writer = writer
}

// MARK: LogHandler Conformance

public var logLevel: Logger.Level = .info

public var metadata: Logger.Metadata = [:]

public subscript(metadataKey key: String) -> Logger.Metadata.Value? {
get { metadata[key] }
set { metadata[key] = newValue }
}

public func log(event: LogEvent) {
let logMessage = LogFormatter.format(label: label, event: event, handlerMetadata: metadata, timestamp: now)
writer.enqueue(logMessage: "\(logMessage)\n")
}
}
Loading
Loading