vapor-docs/docs/basics/errors.zh.md

198 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 错误
Vapor 的错误处理基于 Swift 的 `Error` 协议。路由处理可以通过 `throw` 抛出或返回 `EventLoopFuture` 对象。抛出或返回 Swift 的 `Error` 将导致`500`状态响应并记录错误。`AbortError` 和 `DebuggableError` 分别用于更改响应结果和记录日志。错误的处理由 `ErrorMiddleware` 中间件完成。此中间件默认添加到应用程序中,如果需要,可以用自定义逻辑替换
## 中断(Abort)
Vapor 提供了名为 `Abort` 的默认错误结构。该结构遵循 `AbortError``DebuggableError` 协议。你可以使用 HTTP 状态和可选的失败原因对其进行初始化。
```swift
// 404 错误,默认原因”未找到“。
throw Abort(.notFound)
// 401 错误,自定义错误原因。
throw Abort(.unauthorized, reason: "Invalid Credentials")
```
在旧的异步情况下不支持抛出错误,你必须返回一个 `EventLoopFuture`,就像在 `flatMap` 闭包中一样,你可以返回一个失败的 future。
```swift
guard let user = user else {
req.eventLoop.makeFailedFuture(Abort(.notFound))
}
return user.save()
```
Vapor 提供了一个辅助扩展,用于解包具有可选值的 future 对象:`unwrap(or:)`。
```swift
User.find(id, on: db)
.unwrap(or: Abort(.notFound))
.flatMap
{ user in
// 非可选,提供给闭包的用户。
}
```
如果 `User.find` 返回 `nil`future 将因提供的错误而失败。否则,`flatMap` 将提供一个非可选值。如果使用 `async`/`await` 那么你可以正常处理可选值:
```swift
guard let user = try await User.find(id, on: db) {
throw Abort(.notFound)
}
```
## 中断错误
默认情况下,路由闭包抛出或返回的任何 Swift 的 `Error` 都会导致`500 服务器内部错误`的响应。在调试模式下构建时,`ErrorMiddleware` 中间件将包含错误描述。当项目以发布模式构建时,出于安全原因将其删除。
要配置生成的 HTTP 状态响应或特定错误的原因,请将其遵循 `AbortError` 协议。
```swift
import Vapor
enum MyError {
case userNotLoggedIn
case invalidEmail(String)
}
extension MyError: AbortError {
var reason: String {
switch self {
case .userNotLoggedIn:
return "User is not logged in."
case .invalidEmail(let email):
return "Email address is not valid: \(email)."
}
}
var status: HTTPStatus {
switch self {
case .userNotLoggedIn:
return .unauthorized
case .invalidEmail:
return .badRequest
}
}
}
```
## 调试错误
`ErrorMiddleware` 中间件使用 `Logger.report(error:)` 方法记录路由抛出的错误。此方法将检查是否遵循 `CustomStringConvertible``LocalizedError` 等协议,以记录可读消息。
要自定义错误日志记录,你可以遵循 `DebuggableError` 协议。该协议包括许多有用的属性,例如唯一标识符、源位置和堆栈跟踪。大多数这些属性都是可选的,这使得采用一致性变得容易。
为了更好的遵循 `DebuggableError` 协议,你的错误应该是一个结构,以便它可以在需要时存储源和堆栈跟踪信息。下面是上述 `MyError` 枚举的示例,更新为使用 `struct` 并捕获错误源信息。
```swift
import Vapor
struct MyError: DebuggableError {
enum Value {
case userNotLoggedIn
case invalidEmail(String)
}
var identifier: String {
switch self.value {
case .userNotLoggedIn:
return "userNotLoggedIn"
case .invalidEmail:
return "invalidEmail"
}
}
var reason: String {
switch self.value {
case .userNotLoggedIn:
return "User is not logged in."
case .invalidEmail(let email):
return "Email address is not valid: \(email)."
}
}
var value: Value
var source: ErrorSource?
init(
_ value: Value,
file: String = #file,
function: String = #function,
line: UInt = #line,
column: UInt = #column
) {
self.value = value
self.source = .init(
file: file,
function: function,
line: line,
column: column
)
}
}
```
`DebuggableError` 协议有几个其他属性,如 `possibleCauses``suggestedFixes` 你可以使用它们来提高错误的可调试性。查看协议本身以获取更多信息。
## 堆栈跟踪
Vapor 支持查看正常 Swift 错误和崩溃的堆栈跟踪。
### Swift 回溯
在 Linux 上当出现致命错误或断言时Vapor 使用 [SwiftBacktrace](https://github.com/swift-server/swift-backtrace) 库提供堆栈跟踪。为了让它正常工作,你的应用程序必须在编译过程中包含调试符号。
```sh
swift build -c release -Xswiftc -g
```
### 错误跟踪
默认情况下,`Abort` 将在初始化时捕获当前堆栈跟踪。你的自定义错误类型可以通过遵循 `DebuggableError` 协议并存储 `StackTrace.capture()` 来实现。
```swift
import Vapor
struct MyError: DebuggableError {
var identifier: String
var reason: String
var stackTrace: StackTrace?
init(
identifier: String,
reason: String,
stackTrace: StackTrace? = .capture()
) {
self.identifier = identifier
self.reason = reason
self.stackTrace = stackTrace
}
}
```
当你的应用程序的[日志级别](logging.zh.md#level)设置为 `.debug` 或更低时,错误堆栈跟踪将包含在日志输出中。
当日志级别大于 `.debug` 时,不会捕获堆栈跟踪。要覆盖此行为,请在 `StackTrace.isCaptureEnabled` 中手动设置 `configure`
```swift
// 无论日志级别如何,始终捕获堆栈跟踪。
StackTrace.isCaptureEnabled = true
```
## 错误中间件
`ErrorMiddleware` 是默认添加到应用程序的唯一中间件。该中间件将路由处理抛出或返回的 Swift 错误转换为 HTTP 响应。如果没有这个中间件,抛出的错误将导致连接被关闭而没有响应。
要定制 `AbortError``DebuggableError` 所提供的错误处理之外的错误处理,你可以用自己的错误处理逻辑替换 `ErrorMiddleware` 中间件。要做到这一点,首先通过设置 `app.middleware` 为空删除默认的错误中间件。然后,将你自己的错误处理中间件作为第一个中间件添加到应用程序中。
```swift
// 移除已存在的中间件。
app.middleware = .init()
// 首先添加自定义错误中间件。
app.middleware.use(MyErrorMiddleware())
```
很少有中间件应该放在错误处理中间件*之前*。但 `CORSMiddleware` 中间件不适用该规则。