diff --git a/Sources/App/Core/CFRayRouteLoggingMiddleware.swift b/Sources/App/Core/CFRayRouteLoggingMiddleware.swift new file mode 100644 index 000000000..ca5d5449e --- /dev/null +++ b/Sources/App/Core/CFRayRouteLoggingMiddleware.swift @@ -0,0 +1,34 @@ +// Copyright Dave Verwer, Sven A. Schmidt, and other contributors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Vapor + + +// Replica of the Vapor RouteLoggingMiddleware that's tweaked to explicitly expose the cf-ray header in the logger metadata for the request. +public final class CFRayRouteLoggingMiddleware: Middleware { + public let logLevel: Logger.Level + + public init(logLevel: Logger.Level = .info) { + self.logLevel = logLevel + } + + public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { + guard let cfray = request.headers.first(name: "cf-ray") else { + return next.respond(to: request) + } + request.logger[metadataKey: "cf-ray"] = .string(cfray) + request.logger.log(level: self.logLevel, "\(request.method) \(request.url.path.removingPercentEncoding ?? request.url.path)") + return next.respond(to: request) + } +} diff --git a/Sources/App/Core/Dependencies/EnvironmentClient.swift b/Sources/App/Core/Dependencies/EnvironmentClient.swift index 836473183..3ab254de9 100644 --- a/Sources/App/Core/Dependencies/EnvironmentClient.swift +++ b/Sources/App/Core/Dependencies/EnvironmentClient.swift @@ -44,6 +44,7 @@ struct EnvironmentClient { var hideStagingBanner: @Sendable () -> Bool = { XCTFail("hideStagingBanner"); return Constants.defaultHideStagingBanner } var loadSPIManifest: @Sendable (String) -> SPIManifest.Manifest? var maintenanceMessage: @Sendable () -> String? + var enableCFRayLogging: @Sendable () -> Bool = { XCTFail("enableCFRayLogging"); return true } var mastodonCredentials: @Sendable () -> Mastodon.Credentials? var metricsPushGatewayUrl: @Sendable () -> String? var plausibleBackendReportingSiteID: @Sendable () -> String? @@ -116,6 +117,10 @@ extension EnvironmentClient: DependencyKey { loadSPIManifest: { path in SPIManifest.Manifest.load(in: path) }, maintenanceMessage: { Environment.get("MAINTENANCE_MESSAGE").flatMap(\.trimmed) + enableCFRayLogging: { + Environment.get("ENABLE_CF_RAY_LOGGING") + .flatMap(\.asBool) + ?? false }, mastodonCredentials: { Environment.get("MASTODON_ACCESS_TOKEN") @@ -172,6 +177,7 @@ extension EnvironmentClient: TestDependencyKey { mock.hideStagingBanner = { false } mock.siteURL = { "http://localhost:8080" } mock.shouldFail = { @Sendable _ in false } + mock.enableCFRayLogging = { true } return mock } } diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index c8a65988a..f48f450bd 100644 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Dependencies import Fluent import FluentPostgresDriver import Vapor @@ -37,6 +38,17 @@ public func configure(_ app: Application) async throws -> String { // app.http.server.configuration.responseCompression = .enabled // app.http.server.configuration.requestDecompression = .enabled + @Dependency(\.environment) var environment + + if environment.enableCFRayLogging() { + // This stanza replaces the default middleware from Vapor, specifically replacing the + // default route logging middleware with the tweaked up version that reports on the cf-ray + // header (if any) from CloudFlare. + app.middleware = Middlewares() + app.middleware.use(Vapor.ErrorMiddleware.default(environment: app.environment)) + app.middleware.use(CFRayRouteLoggingMiddleware()) + } + app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) app.middleware.use(ErrorMiddleware()) diff --git a/app.yml b/app.yml index a0b515fbf..c34f9ab63 100644 --- a/app.yml +++ b/app.yml @@ -44,6 +44,7 @@ x-shared: &shared DATABASE_USERNAME: ${DATABASE_USERNAME} DATABASE_PASSWORD: ${DATABASE_PASSWORD} DATABASE_USE_TLS: ${DATABASE_USE_TLS} + ENABLE_CF_RAY_LOGGING: ${ENABLE_CF_RAY_LOGGING} FAILURE_MODE: ${FAILURE_MODE} GITHUB_TOKEN: ${GITHUB_TOKEN} GITLAB_API_TOKEN: ${GITLAB_API_TOKEN}