diff --git a/Package.swift b/Package.swift index 3c49f29..7e5367c 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,7 @@ let package = Package( // https://github.com/Joannis/SMTPKitten <- No updates since 0ct 2020 // https://github.com/Joannis/VaporSMTPKit <- No updates since 0ct. 2020, uses SMTPKitten .package(url: "https://github.com/Kitura/Swift-SMTP", from: "6.0.0"), - .package(url: "https://github.com/christophhagen/clairvoyant.git", from: "0.4.0"), + .package(url: "https://github.com/christophhagen/clairvoyant.git", branch: "main"), ], targets: [ .target( diff --git a/Sources/App/Management/SQLiteDatabase.swift b/Sources/App/Management/SQLiteDatabase.swift index e70c382..1d09946 100644 --- a/Sources/App/Management/SQLiteDatabase.swift +++ b/Sources/App/Management/SQLiteDatabase.swift @@ -51,17 +51,15 @@ final class SQLiteDatabase { private let registeredPlayerCountMetric: Metric - init(database: Database, mail: Configuration.EMail?) { - self.registeredPlayerCountMetric = Metric( + init(database: Database, mail: Configuration.EMail?) async throws { + self.registeredPlayerCountMetric = try await Metric( "schafkopf.players", name: "Number of users", description: "The total number of user accounts") - self.tables = TableManagement(database: database) + self.tables = try await TableManagement(database: database) self.mail = mail?.mailConfig - Task { - await updateRegisteredPlayerCount(from: database) - } + await updateRegisteredPlayerCount(from: database) } func registerPlayer(named name: PlayerName, hash: PasswordHash, email: String?, in database: Database) async throws -> SessionToken { @@ -92,7 +90,7 @@ final class SQLiteDatabase { func updateRegisteredPlayerCount(from database: Database) async { do { let count = try await User.query(on: database).count() - registeredPlayerCountMetric.update(count) + try? await registeredPlayerCountMetric.update(count) } catch { log("Failed to update player count metric: \(error)") } diff --git a/Sources/App/Management/TableManagement.swift b/Sources/App/Management/TableManagement.swift index 2248d9c..4f5c191 100644 --- a/Sources/App/Management/TableManagement.swift +++ b/Sources/App/Management/TableManagement.swift @@ -27,26 +27,25 @@ final class TableManagement { Load the tables from a file in the storage folder - Throws: Errors when the file could not be read */ - init(database: Database) { - self.tableCountMetric = .init( + init(database: Database) async throws { + self.tableCountMetric = try await .init( "schafkopf.tables", name: "Open tables", description: "The number of currently available tables") - self.playingPlayerCountMetric = .init( + self.playingPlayerCountMetric = try await .init( "schafkopf.playing", name: "Sitting players", description: "The number of players currently sitting at a table") - self.connectedPlayerCountMetric = .init( + self.connectedPlayerCountMetric = try await .init( "schafkopf.connected", name: "Connected players", description: "The number of players with a websocket connection to the server") - Task { - do { - try await loadTables(from: database) - } catch { - log("Failed to load tables: \(error)") - } + + do { + try await loadTables(from: database) + } catch { + log("Failed to load tables: \(error)") } } @@ -60,23 +59,23 @@ final class TableManagement { self.tables[id] = WaitingTable(id: id, name: table.name, isPublic: table.isPublic, players: table.players) } log("\(tables.count) tables loaded") - logTableCount() - logPlayingPlayerCount() - logConnectedPlayerCount() + await logTableCount() + await logPlayingPlayerCount() + await logConnectedPlayerCount() } - private func logTableCount() { - tableCountMetric.update(tables.count) + private func logTableCount() async { + try? await tableCountMetric.update(tables.count) } - private func logPlayingPlayerCount() { + private func logPlayingPlayerCount() async { let count = tables.values.sum { $0.playerCount } - playingPlayerCountMetric.update(count) + try? await playingPlayerCountMetric.update(count) } - private func logConnectedPlayerCount() { + private func logConnectedPlayerCount() async { let count = tables.values.sum { $0.numberOfConnectedPlayers } - connectedPlayerCountMetric.update(count) + try? await connectedPlayerCountMetric.update(count) } /** @@ -93,8 +92,8 @@ final class TableManagement { try await player.update(on: database) let waitingTable = WaitingTable(newTable: table, user: player) self.tables[waitingTable.id] = waitingTable - logTableCount() - logPlayingPlayerCount() + await logTableCount() + await logPlayingPlayerCount() return waitingTable.tableInfo(forPlayer: player.name) } @@ -127,7 +126,7 @@ final class TableManagement { player.$table.id = table.id try await player.update(on: database) table.sendUpdateToAllPlayers() - logPlayingPlayerCount() + await logPlayingPlayerCount() return table.tableInfo(forPlayer: player.name) } @@ -159,7 +158,7 @@ final class TableManagement { player.$table.id = nil guard let table = WaitingTable(oldTable: oldTable, removing: player.name) else { tables[oldTable.id] = nil - logTableCount() + await logTableCount() try await player.update(on: database) try await Table.query(on: database).filter(\.$id == oldTable.id).delete() return @@ -168,8 +167,8 @@ final class TableManagement { tables[table.id] = table #warning("Update points for all players, add penalty if running game") table.sendUpdateToAllPlayers() - logPlayingPlayerCount() - logConnectedPlayerCount() + await logPlayingPlayerCount() + await logConnectedPlayerCount() try await player.update(on: database) } @@ -177,16 +176,21 @@ final class TableManagement { guard let table = currentTable(for: player) else { return false } - defer { logConnectedPlayerCount() } - return table.connect(player: player, using: socket) + let result = table.connect(player: player, using: socket) + Task { + await logConnectedPlayerCount() + } + return result } func disconnect(player: PlayerName) { guard let table = currentTable(for: player) else { return } - defer { logConnectedPlayerCount() } table.disconnect(player: player) + Task { + await logConnectedPlayerCount() + } } func performAction(player: PlayerName, action: PlayerAction) -> PlayerActionResult { @@ -255,6 +259,8 @@ final class TableManagement { func disconnectAllSockets() { tables.values.forEach { $0.disconnectAllPlayers() } - logConnectedPlayerCount() + Task { + await logConnectedPlayerCount() + } } } diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index a7e6126..697c03a 100644 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -5,24 +5,24 @@ import Clairvoyant var server: SQLiteDatabase! // configures your application -public func configure(_ app: Application) throws { +public func configure(_ app: Application) async throws { let storageFolder = URL(fileURLWithPath: app.directory.resourcesDirectory) let logFolder = storageFolder.appendingPathComponent("logs") let accessManager = AccessTokenManager([]) - let monitor = MetricObserver( + let monitor = await MetricObserver( logFolder: logFolder, accessManager: accessManager, logMetricId: "schafkopf.log") MetricObserver.standard = monitor - let status = Metric( + let status = try! await Metric( "schafkopf.status", name: "Status", description: "The main status of the server") - status.update(.initializing) - monitor.registerRoutes(app) + try? await status.update(.initializing) + await monitor.registerRoutes(app) let configPath = URL(fileURLWithPath: app.directory.resourcesDirectory) .appendingPathComponent("config.json") @@ -31,8 +31,8 @@ public func configure(_ app: Application) throws { do { configuration = try Configuration(loadFromUrl: configPath) } catch { - status.update(.initializationFailure) - monitor.log("Failed to read configuration: \(error)") + try? await status.update(.initializationFailure) + await monitor.log("Failed to read configuration: \(error)") // Note: If configuration can't be loaded, then the server will run on the wrong port // and access to metrics is impossible, since no tokens are loaded return @@ -60,8 +60,8 @@ public func configure(_ app: Application) throws { do { try app.autoMigrate().wait() } catch { - monitor.log("Failed to migrate database: \(error)") - status.update(.initializationFailure) + await monitor.log("Failed to migrate database: \(error)") + try? await status.update(.initializationFailure) return } @@ -69,7 +69,7 @@ public func configure(_ app: Application) throws { app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) let db = app.databases.database(.sqlite, logger: .init(label: "Init"), on: app.databases.eventLoopGroup.next())! - server = SQLiteDatabase(database: db, mail: configuration.mail) + server = try await SQLiteDatabase(database: db, mail: configuration.mail) // Gracefully shut down by closing potentially open socket DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + .seconds(5)) { @@ -81,10 +81,15 @@ public func configure(_ app: Application) throws { // register routes routes(app) - status.update(.nominal) + try? await status.update(.nominal) } func log(_ message: String) { - MetricObserver.standard?.log(message) - print(message) + guard let observer = MetricObserver.standard else { + print(message) + return + } + Task { + await observer.log(message) + } } diff --git a/Sources/Run/main.swift b/Sources/Run/main.swift index 373be5f..8cfcf58 100644 --- a/Sources/Run/main.swift +++ b/Sources/Run/main.swift @@ -5,5 +5,7 @@ var env = try Environment.detect() try LoggingSystem.bootstrap(from: &env) let app = Application(env) defer { app.shutdown() } -try configure(app) -try app.run() +Task { + try await configure(app) + try app.run() +}