news 2026/8/22 23:30:36

第九篇:Ktor Plugin 实战:Logging、HttpTimeout 与 HttpRequestRetry

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第九篇:Ktor Plugin 实战:Logging、HttpTimeout 与 HttpRequestRetry

前面几篇,我已经把 Ktor 网络层逐渐搭到了这里:

ApiService ↓ NetworkClient ↓ NetworkConnectivityProvider ↓ Pre-check ↓ HttpClient ↓ Engine ↓ HTTP

同时也建立了异常体系:

Network Timeout HTTP Parse Business Unknown ↓ ExceptionMapper ↓ AppError

现在一个请求已经能够:

发送 Request ↓ 接收 Response ↓ 解析 JSON ↓ 检查业务 code ↓ 统一异常

但一个真正可以投入项目使用的网络层,还需要三个非常重要的能力:

Logging ↓ 这次 HTTP 请求到底发生了什么? HttpTimeout ↓ 这次请求最多允许等待多久? HttpRequestRetry ↓ 请求失败以后,什么时候值得再试一次?

这三个能力都可以通过 Ktor Client Plugin 来完成。

截至当前 Ktor 官方文档 3.5.2,这三个能力分别对应LoggingHttpTimeoutHttpRequestRetry

这一篇不打算把每个 Plugin 的所有 API 都列一遍。

真正要搞清楚的是:

Logging、Timeout、Retry 在一次 Request 生命周期中分别承担什么职责,以及它们之间如何配合。


一、先重新理解 Plugin

前面已经讲过:

HttpClient + Plugin + Engine

是 Ktor Client 很重要的设计。

例如:

val client = HttpClient { install(Logging) install(HttpRequestRetry) install(HttpTimeout) }

可以理解成:

HttpClient │ ├── Logging │ ↓ │ 增加 HTTP 日志能力 │ ├── HttpRequestRetry │ ↓ │ 增加失败重试能力 │ └── HttpTimeout ↓ 增加超时控制能力

Plugin 不是:

UserApi 调一次 OrderApi 再调一次 RobotApi 再调一次

而是:

安装到某一个 HttpClient 上,为这个 Client 增加统一网络能力。

所以它首先是:

Client Scope

的配置。


二、三个 Plugin 不要混在一起

先把三个问题彻底分开。

Logging

回答:

发生了什么?

例如:

请求 URL 是什么? Method 是什么? Header 有没有? 服务器返回什么 Status? Response 是什么?

HttpTimeout

回答:

最多允许等多久?

HttpRequestRetry

回答:

失败以后,要不要重新发一次?

所以:

Logging ≠ Timeout ≠ Retry

可以简单记:

Logging 负责看见,Timeout 负责限制等待,Retry 负责决定是否再试。


三、第一个 Plugin:Logging

安装最简单:

install(Logging)

Ktor 官方的LoggingPlugin 用于记录 HTTP Call,并且可以配置:

logger level filter sanitizeHeader bodyFilter

其中LoggingConfig当前明确提供loggerlevelfilter()sanitizeHeader()bodyFilter


四、最简单的系统 Logging

例如:

install(Logging) { logger = Logger.DEFAULT level = LogLevel.ALL }

这里:

Logging Plugin ↓ 采集 HTTP Request / Response 信息 ↓ Logger.DEFAULT ↓ 输出日志

在 JVM 平台,Logger.DEFAULT使用 SLF4J;Android 官方文档推荐配合slf4j-android。Ktor Multiplatform 也支持自己提供 Logger。


五、一个真实 Logging 例子

假设:

POST /login

真实 Request:

POST /login Content-Type: application/json Authorization: Bearer abc123 { "username": "tom", "password": "123456" }

配置:

install(Logging) { logger = Logger.DEFAULT level = LogLevel.ALL sanitizeHeader { headerName -> headerName == HttpHeaders.Authorization } }

那么日志中:

Authorization: ***

真实 Token 不再直接出现。

而真正发给服务器的 Request 仍然是:

Authorization: Bearer abc123

也就是说:

sanitizeHeader ↓ 只影响日志展示 不会修改真实 HttpRequest

Ktor 官方当前的sanitizeHeader()就是用来把指定敏感 Header 的日志值替换掉,默认占位符为***


六、这里马上出现一个问题:password 怎么办?

刚才 Request Body:

{ "username": "tom", "password": "123456" }

虽然:

Authorization ↓ ***

但是:

password

仍然在 Body 中。

因为:

sanitizeHeader

只处理:

Header

不能处理:

JSON Body

Ktor 3.5.x 的LoggingConfig还提供了:

bodyFilter

LogBodyFilter当前同时有filterRequest()filterResponse(),可以决定 Request/Response Body 如何进入日志,例如脱敏、截断或者跳过。

这一篇不继续展开 Body 脱敏,否则 Logging 会占掉整篇文章。

Tips:sanitizeHeaderbodyFilter、JSON Body 脱敏、Binary/Multipart 跳过、大 Body 截断、Logger.DEFAULT与 Custom Logger 的完整设计,

放到补充篇9.1《Ktor Logging 深入:Header、Body 脱敏与自定义 Logger》单独讲。


七、Logger.DEFAULT 和 Custom Logger 有什么区别?

这个主篇只建立基本认识。

方案 A:

install(Logging) { logger = Logger.DEFAULT level = LogLevel.ALL sanitizeHeader { it == HttpHeaders.Authorization } }

流程:

Ktor Logging ↓ LoggingConfig ↓ 脱敏 / 过滤 ↓ Logger.DEFAULT ↓ 平台日志系统

方案 B:

install(Logging) { logger = object : Logger { override fun log( message: String, ) { AppLogger.d( tag = "HTTP", message = message, ) } } level = LogLevel.ALL sanitizeHeader { it == HttpHeaders.Authorization } }

流程:

Ktor Logging ↓ LoggingConfig ↓ 脱敏 / 过滤 ↓ Custom Logger ↓ AppLogger / Kermit

所以:

两种方式都可以使用 Ktor 自带的脱敏能力。

区别主要在:

Logger.DEFAULT ↓ 使用默认日志出口 Custom Logger ↓ 把 Ktor 日志接入自己的日志系统

Ktor 官方同样支持通过实现Logger.log(message)提供 Custom Logger。


八、Logging 在架构中应该负责什么?

最终可以理解:

Logging Plugin ↓ 采集 HTTP 信息 LoggingConfig ↓ 决定记录什么 ↓ level filter sanitizeHeader bodyFilter Logger ↓ 决定日志输出到哪里

所以:

ApiService

不应该:

println("POST /login")

到处自己打印网络日志。

统一 HTTP 日志应该属于:

HttpClient ↓ Logging Plugin

九、第二个 Plugin:HttpTimeout

接下来是:

HttpTimeout

它解决的问题非常简单:

网络请求不能无限等下去。

例如:

install(HttpTimeout) { requestTimeoutMillis = 15_000 connectTimeoutMillis = 10_000 socketTimeoutMillis = 15_000 }

Ktor 官方当前把 Timeout 分为:

Request Timeout Connect Timeout Socket Timeout

三类。


十、Connect Timeout

首先:

Connect Timeout

负责:

建立服务器连接最多允许等待多久。

例如:

GET /orders ↓ 尝试连接 api.example.com ↓ 一直连接不上 ↓ 超过 10 秒 ↓ ConnectTimeoutException

如果:

connectTimeoutMillis = 10_000

意思就是:

建立连接 ↓ 最多等 10 秒

可以记成:

Connect Timeout = 门一直敲不开,我最多等多久。


十一、Socket Timeout

假设:

连接已经成功

但是:

服务器迟迟没有继续发送数据

这时候关注:

Socket Timeout

Ktor 官方把它定义成:

数据交换过程中,两个数据包之间允许的最大无数据活动时间。

例如:

连接成功 ↓ 服务器开始返回数据 ↓ 突然 20 秒没有任何数据 ↓ Socket Timeout

可以记:

Socket Timeout = 已经接通了,但你多久不说话我就不等了。


十二、Request Timeout

最后:

Request Timeout

范围最大。

它关注:

发送 Request ↓ 一直到接收 Response

整笔 HTTP Call 所允许的时间。

可以画成:

Request Timeout ┌──────────────────────────┐ ↓ ↓ Request → Connect → 数据交换 → Response ↑ ↑ │ │ Connect Socket Timeout Timeout

可以记:

Request Timeout = 整笔请求最多允许花多久。


十三、一个 Timeout 实际例子

配置:

install(HttpTimeout) { requestTimeoutMillis = 15_000 connectTimeoutMillis = 10_000 socketTimeoutMillis = 15_000 }

请求:

GET /orders

情况一:

2 秒建立连接 ↓ 3 秒返回数据 ↓ 总计 5 秒 ↓ 成功

情况二:

开始连接 ↓ 超过 10 秒仍然建立不了连接 ↓ Connect Timeout

情况三:

2 秒连接成功 ↓ 服务器开始返回 ↓ 中途超过 15 秒没有数据 ↓ Socket Timeout

情况四:

连接 + 数据传输 ↓ 整笔 Call 超过 Request Timeout ↓ Request Timeout

这样三个 Timeout 就比较容易区分了。


十四、Timeout 也存在“Client 默认值 + Request 特殊值”

例如普通接口:

GET /orders GET /users GET /products

统一:

install(HttpTimeout) { requestTimeoutMillis = 15_000 }

但是上传:

POST /upload

可能明显需要更长时间。

于是:

client.post("upload") { timeout { requestTimeoutMillis = 120_000 } }

这一次 Request:

120 秒

普通 Request:

15 秒

Ktor 官方当前明确支持 Request 级 timeout,并且 Request 配置会覆盖 Client Plugin 的全局对应 timeout。

这正好又对应我们前面讲过的:

Client 默认配置 ↓ 大多数 Request Request 特殊配置 ↓ 只影响当前请求

十五、Timeout 还存在 Engine 差异

KMP 这里要特别注意。

commonMain 可以统一配置:

Request Connect Socket

但不同 Engine 的底层能力不一定完全一致。

例如当前 Ktor 官方文档中:

Darwin Request ✅ Connect ❌ Socket ✅ JavaScript Request ✅ Connect ❌ Socket ❌

所以:

KMP 可以统一 Timeout 配置思想,但不能理解成所有平台 Engine 的底层 Timeout 能力完全一致。

这和 ConnectivityProvider 的道理一样:

统一的是抽象 ≠ 所有平台实现完全相同

十六、Timeout 最终怎么进入异常体系?

Ktor 当前可能抛:

HttpRequestTimeoutException ConnectTimeoutException SocketTimeoutException

如果业务没有必要区分这么细:

三种 Ktor Exception ↓ ExceptionMapper ↓ AppError.Timeout

即可。

ViewModel 不需要:

catch ( e: SocketTimeoutException )

十七、第三个 Plugin:HttpRequestRetry

接下来进入最容易写出问题的:

HttpRequestRetry

它解决:

这次 Request 失败以后,要不要重新发送一次?

首先有一个非常重要的前提:

请求失败,不等于应该 Retry。

Ktor Client 默认不会因为网络或服务器错误自动替你反复请求;安装HttpRequestRetry后才可以配置 Retry 次数、条件、Delay 以及 Retry 前如何修改 Request。


十八、先看最简单的 Retry

例如:

install(HttpRequestRetry) { retryOnServerErrors( maxRetries = 2, ) exponentialDelay() }

这里:

5xx ↓ 允许 Retry maxRetries = 2 ↓ 最多重试 2 次 exponentialDelay() ↓ Retry 之间不要立即连续请求

Ktor 官方retryOnServerErrors()当前就是用于 5xx Response 重试,而exponentialDelay()用于指数退避。


十九、一个 503 Retry 的真实例子

例如:

GET /products

第一次:

Request #1 ↓ 503 Service Unavailable

服务器可能正在:

瞬时负载过高 滚动发布 网关临时异常

于是:

等待 ↓ Retry

第二次:

Request #2 ↓ 200 OK

整个流程:

GET /products ↓ 503 ↓ HttpRequestRetry ↓ 等待 ↓ GET /products ↓ 200 ↓ 继续正常业务流程

这时候:

ViewModel

甚至根本不需要知道:

第一次出现过 503

这就是 Retry 的一个重要价值:

在最终错误暴露给业务层之前,消化某些瞬时故障。


二十、为什么不能所有 5xx 都无脑 Retry?

因为 Retry 不只是:

Response 出错了吗?

还要问:

这笔 Request 再发一次安全吗?

这就涉及:

幂等性

二十一、最重要的 Retry 反例:创建订单

例如:

POST /orders ↓ 创建订单

Client 发送:

POST /orders

服务器:

已经创建 Order #1001

然后 Response 返回途中:

网络断开

Client 最终看到:

Network Error

或者:

Timeout

Client 很容易误认为:

“请求失败了”

然后自动 Retry:

POST /orders

服务器可能:

再创建 Order #1002

最后:

用户操作一次 ↓ 产生两张订单

所以非常重要的一句话是:

客户端没有收到成功 Response,不代表服务端没有执行成功。


二十二、这就是为什么 Retry 必须考虑幂等性

简单理解:

同一个请求执行一次和执行多次,最终业务结果应该一致。

例如规范的:

GET /users

多执行几次:

只是查询

通常风险比较低。

而:

POST /orders

多执行一次可能产生:

第二张订单

所以:

失败 ↓ 不能直接推导出 ↓ Retry

而应该:

失败 ↓ 错误是否值得重试? ↓ Request 能安全重放吗? ↓ 都满足 ↓ Retry

二十三、POST 怎么才能安全 Retry?

一种常见设计:

Idempotency-Key

例如:

Idempotency-Key: order-100001

第一次:

POST /orders Key = ABC ↓ 服务器创建 Order #1001 ↓ 记录 ABC 已经处理

Retry:

POST /orders Key 仍然 = ABC

服务器:

发现 ABC 已经处理 ↓ 不创建第二张订单 ↓ 返回原结果

这时候:

客户端 Retry + 服务端幂等

才真正形成一套可靠机制。

所以:

HttpRequestRetry 只能负责“再发送”,真正能不能安全再发送,还取决于业务协议设计。


二十四、机器人控制指令更不能无脑 Retry

例如:

向前移动 1 米

Client:

Command #1001 ↓ Server / Robot ↓ 已经执行

但:

Ack 丢失

Client:

没收到 ↓ Retry

如果 Retry 被当成:

新命令

机器人可能:

再移动 1 米

所以机器人领域通常还需要:

CommandId Sequence Ack Result 幂等处理

HttpRequestRetry 本身无法替你解决这些业务协议问题。


二十五、Retry 判断最好拆成三个问题

以后看到任何 Retry,都可以问:

第一问

这个错误值得 Retry 吗?

例如:

Network Error Timeout 503

可能值得。

而:

404 Parse Error Business Error

通常没有意义自动 Retry。


第二问

当前还有网络条件吗?

也就是:

NetworkConnectivityProvider ↓ Available ?

第三问

Request 能安全重放吗?

例如:

GET 幂等 PUT Idempotency-Key Command Sequence

最终:

Retryable Error AND Connectivity Available AND Request Safe ↓ Retry

这是比:

maxRetries = 3

重要得多的 Retry 模型。


二十六、Retry 也可以根据 Exception 判断

Ktor 当前支持:

retryOnExceptionIf { request, cause, -> ... }

官方示例就是根据 Request 和 Throwable 自定义是否 Retry。

例如:

retryOnExceptionIf( maxRetries = 2, ) { request, cause -> val safeRequest = request.method == HttpMethod.Get val networkAvailable = connectivityProvider .isNetworkAvailable val retryableError = isRetryableNetworkException( cause ) safeRequest && networkAvailable && retryableError }

这个例子非常重要。

因为 Retry 已经不只是:

Exception ↓ Retry

而是:

Exception + Request + Provider 当前状态 ↓ 共同决定

二十七、这里正好连接 8.1 的动态 Provider

补充篇 8.1:Ktor/KMP 断网处理:为什么请求前要先判断网络状态?

第一次 Request:

Provider = Available ↓ NetworkClient Pre-check ↓ 通过 ↓ HttpClient

请求失败:

Timeout

但是准备 Retry 时:

Wi-Fi 已经断开 ↓ Provider = Unavailable

这时候:

retryOnExceptionIf ↓ 再次读取 Provider ↓ false ↓ 停止 Retry

流程:

Request #1 ↓ Provider = Available ↓ 开始请求 ↓ Timeout ↓ 准备 Retry ↓ 读取同一个 Provider ↓ Provider 已经变成 Unavailable ↓ 不 Retry

这就真正体现了:

HttpClient 持有的是动态 Provider 对象,不是创建 Client 时的一次 Boolean 快照。


二十八、为什么 NetworkClient 的 Pre-check 不够?

因为现在结构:

NetworkClient ↓ Pre-check ↓ HttpClient ↓ HttpRequestRetry

Pre-check:

只在第一次进入 HttpClient 之前执行

但 HttpRequestRetry:

是在 HttpClient 内部重新发送

所以:

Request #1 ↓ 经过 NetworkClient Pre-check Retry #1 ↓ 不会天然重新回到 NetworkClient Pre-check

因此如果你希望:

每次 Retry ↓ 都考虑最新 Connectivity

Retry Condition 自己重新读取:

NetworkConnectivityProvider

是比较自然的方案。


二十九、Retry Delay 为什么不能省?

假设服务器:

503

你马上:

Retry Retry Retry

可能会:

服务器已经压力很大 ↓ 客户端继续猛打 ↓ 情况更差

所以 Retry 通常要:

Backoff

例如:

第一次等待 1 秒 第二次等待更久 第三次继续增加

Ktor 当前官方基础示例提供:

exponentialDelay()

用于指数退避。

核心思想:

瞬时错误值得再试,但不要立即连续轰炸服务器。


三十、HttpRequestRetry 和 HttpTimeout 的安装顺序

这里有一个 Ktor 当前明确要求的细节。

如果:

HttpRequestRetry + HttpTimeout

一起使用,并且希望 Retry 能够配置处理 Timeout:

HttpClient { install(HttpRequestRetry) { ... } install(HttpTimeout) { ... } }

也就是:

HttpRequestRetry ↓ 先 install HttpTimeout ↓ 后 install

Ktor 3.5.2 官方文档明确说明了这个顺序。

这里不要按照:

“哪个先发生就哪个先 install”

去推理。

Plugin 安装顺序和 Ktor Pipeline 的组合有关。

这个场景直接按官方要求即可。


三十一、Timeout + Retry 的实际例子

假设:

GET /products

配置:

Request Timeout = 15 秒 maxRetries = 2

第一次:

Request #1 ↓ Timeout

准备 Retry:

Provider = Available ↓ GET ↓ Retry Policy 允许 ↓ 等待

第二次:

Request #2 ↓ 200

那么最终:

业务层 ↓ 成功

但这里要注意:

单次 Request Timeout

和:

整个业务操作总耗时

不是一个概念。

因为可能:

Attempt #1 ↓ Timeout Backoff Attempt #2 ↓ Timeout Backoff Attempt #3

所以整个操作的总时间:

可能明显超过一次 requestTimeoutMillis

如果某个业务有严格的:

整个操作最多 20 秒

还可能需要业务层额外的总 Deadline,而不能只依赖单次 HttpTimeout。


三十二、一个比较保守的 Retry 例子

项目早期可以先从:

GET + 5xx

开始。

例如:

install(HttpRequestRetry) { maxRetries = 2 retryIf { request, response -> val safeRequest = request.method == HttpMethod.Get val retryableStatus = response.status.value in 500..599 val networkAvailable = connectivityProvider .isNetworkAvailable safeRequest && retryableStatus && networkAvailable } exponentialDelay() }

这个配置真正表达的是:

GET AND 5xx AND 当前仍然有网络 ↓ Retry

而不是:

5xx ↓ 统统 Retry

三十三、Logging、Timeout、Retry 现在可以放回一条链路

Request ↓ Connectivity Pre-check ↓ HttpClient │ ┌──────────────┼──────────────┐ ↓ ↓ ↓ Logging HttpTimeout HttpRequestRetry ↓ ↓ ↓ 看见过程 时间边界 Retry Policy │ │ │ │ │ ┌──────┼──────┐ │ │ ↓ ↓ ↓ │ │ Error Network Request │ │ State Safety │ │ ↓ └──────────────┴────────────────┘ ↓ Engine ↓ HTTP

一句话:

Logging ↓ 让我知道发生了什么 Timeout ↓ 让我不要无限等待 Retry ↓ 让我在值得的时候再试一次

三十四、它们和 ExceptionMapper 是什么关系?

假设:

Request ↓ Timeout

如果:

Retry Policy ↓ 允许 Retry

先继续 Retry。

直到:

成功

或者:

Retry 用完 ↓ 仍然失败

才:

Throwable ↓ ExceptionMapper ↓ AppError.Timeout

所以:

Retry

负责的是:

在错误最终暴露给上层前,尝试恢复瞬时故障。

而:

ExceptionMapper

负责的是:

最终仍然失败时,把底层异常翻译成稳定的 AppError。


三十五、一个阶段性的 HttpClient 配置

把三个 Plugin 放在一起,可以先写成:

fun createHttpClient( connectivityProvider: NetworkConnectivityProvider, ): HttpClient { return HttpClient { install(HttpRequestRetry) { maxRetries = 2 retryIf { request, response, -> val safeRequest = request.method == HttpMethod.Get val retryableStatus = response.status.value in 500..599 safeRequest && retryableStatus && connectivityProvider .isNetworkAvailable } retryOnExceptionIf { request, cause, -> val safeRequest = request.method == HttpMethod.Get val retryableError = isRetryableNetworkException( cause ) safeRequest && retryableError && connectivityProvider .isNetworkAvailable } exponentialDelay() } install(HttpTimeout) { requestTimeoutMillis = 15_000 connectTimeoutMillis = 10_000 socketTimeoutMillis = 15_000 } install(Logging) { logger = Logger.DEFAULT level = LogLevel.ALL sanitizeHeader { headerName, -> headerName == HttpHeaders.Authorization } } } }

注意:

这只是一个:

阶段性示例

不是:

所有项目的最终标准答案

例如正式项目的 Logging 可能接:

Kermit AppLogger 文件日志 统一脱敏

Retry 也可能根据不同 Client 使用不同策略。


三十六、三个 Plugin 也有 Client 作用域

例如:

apiClient

可能:

Logging = Debug Timeout = 15s GET Retry = 2

而:

uploadClient

可能:

Logging ↓ 不打印二进制 Body Timeout ↓ 120s Retry ↓ 使用上传自己的策略

机器人 Client:

robotClient

可能:

Timeout ↓ 更短 Retry ↓ 不能只看 HTTP ↓ 还要考虑 CommandId / Ack

所以仍然是:

配置不是越集中越好,而是应该放到正确的 Client 作用域。


三十七、本篇最容易踩的几个坑

坑一:Logging 全部LogLevel.ALL直接上正式环境

可能泄露:

Authorization Cookie Password Token 用户隐私

至少需要:

sanitizeHeader bodyFilter 日志环境控制

更完整方案见 9.1。

补充篇 8.1:Ktor/KMP 断网处理:为什么请求前要先判断网络状态?


坑二:认为 Custom Logger 才能脱敏

不对。

Logger.DEFAULT

和:

Custom Logger

都可以使用:

sanitizeHeader bodyFilter

因为它们属于:

LoggingConfig

而不是 Logger 本身。


坑三:把断网当 Timeout

不对。

请求前明确断网 ↓ Network 等待超过限制 ↓ Timeout

坑四:所有 Timeout 都 Retry

不对。

还需要考虑:

当前网络状态 Request 是否安全重放 业务是否允许

坑五:POST Timeout 自动 Retry

非常危险。

因为:

Client 没收到 Response

不代表:

Server 没执行

坑六:Retry 越多越稳定

不对。

过多 Retry 可能:

增加等待 增加服务器压力 制造重复业务

Retry 是解决:

瞬时故障

不是把:

所有失败

硬磨成成功。


坑七:只在 NetworkClient 做 Connectivity Pre-check

第一次请求够用。

但 HttpRequestRetry:

在 HttpClient 内部 Retry

不会天然重新回到:

NetworkClient Pre-check

所以 Retry Condition 如果需要 Connectivity:

重新读取 Provider 当前状态

更加合理。


坑八:忽略 Retry 和 Timeout 的安装顺序

需要 Retry Timeout 时:

HttpRequestRetry ↓ 先安装 HttpTimeout ↓ 后安装

这是 Ktor 当前官方明确要求。


三十八、本篇总结

这一篇最重要的是把三个 Plugin 的职责彻底分开。

Logging

回答:

发生了什么?

结构:

Logging Plugin ↓ LoggingConfig │ ├ level ├ filter ├ sanitizeHeader └ bodyFilter ↓ Logger

Logger.DEFAULT和 Custom Logger 都可以使用 Ktor 自带的过滤/脱敏配置;Custom Logger 只是把处理后的日志接入自己的日志系统。

Tips:完整日志方案见补充篇 9.1《Ktor Logging 深入:Header、Body 脱敏与自定义 Logger》。


HttpTimeout

回答:

允许等多久?

三种:

Connect Timeout ↓ 建立连接最多多久 Socket Timeout ↓ 数据交换过程中 最大无活动时间 Request Timeout ↓ 整笔 HTTP Call 最多多久

并且:

Client 默认 Timeout ↓ 可以被 Request 特殊 Timeout 覆盖

HttpRequestRetry

回答:

失败以后 是否值得再发送一次?

真正可靠的 Retry 不是:

失败 ↓ Retry

而是:

错误值得 Retry? ↓ 当前网络 Available? ↓ Request 可以安全重放? ↓ 三者都满足 ↓ Retry

再配合:

有限次数 + Backoff

处理瞬时故障。


把目前的整个网络可靠性链路串起来:

Request ↓ Connectivity Pre-check ↓ Logging ↓ HttpTimeout ↓ HttpRequestRetry ↓ Engine ↓ HTTP ↓ Response / Throwable ↓ 必要时 Retry ↓ 最终仍失败 ↓ ExceptionMapper ↓ AppError

这一篇真正需要记住四句话:

Connectivity 解决“现在是否值得请求”。

Logging 解决“这次请求到底发生了什么”。

Timeout 解决“我最多愿意等多久”。

Retry 解决“失败以后,这笔请求是否值得而且能够安全地再试一次”。

当这几个职责分开以后,网络层就不再是:

client.get() ↓ 成功 / 失败

这么简单,而开始具备真正的工程可靠性。


补充篇 9.1

《Ktor Logging 深入:Header、Body 脱敏与自定义 Logger》

专门展开:

LoggingConfig 和 Logger 的关系 Logger.DEFAULT Custom Logger sanitizeHeader bodyFilter Request / Response Body 脱敏 JSON 敏感字段递归处理 Binary / Multipart Skip 大 Body 截断 Kermit / AppLogger 对接 项目级二次脱敏 最终完整生产级 Logging 示例

下一篇

《Ktor Auth:Bearer Token、Refresh Token 与 401 自动刷新到底怎么工作?》

下一篇开始解决:

Access Token ↓ 怎么自动加到 Request? 401 ↓ 发生以后谁处理? Refresh Token ↓ 为什么经常需要独立 Client? Refresh 成功 ↓ 原来的 Request 怎么重新发送? 10 个请求同时 401 ↓ 为什么不能刷新 10 次 Token? TokenProvider ↓ 为什么又是一个动态 Provider? Auth Plugin ↓ 和 DefaultRequest 手动添加 Authorization 到底有什么区别?

并把之前的:

apiClient refreshClient TokenProvider 401 Provider 动态状态 Retry

真正连接起来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/22 23:23:48

摘要AI率高但全文正常怎么办?保留四类核心必要信息再调整表达。

摘要AI率高但全文正常怎么办?保留四类核心必要信息再调整表达。 先把这篇文章唯一要解决的问题锁定 当前处境是:全文结果正常,摘要因篇幅短、结构固定而单独出现较高AI率。这篇文章只回答“摘要AI率高但全文正常怎么办?保留四类核…

作者头像 李华
网站建设 2026/8/22 23:23:20

大疆固件逆向全解析:5 个 Python 工具拆开机内加密与签名

大疆固件逆向全解析:5 个 Python 工具拆开机内加密与签名 【免费下载链接】dji_rev DJI Reverse engineering 项目地址: https://gitcode.com/gh_mirrors/dj/dji_rev 这套叫 dji_rev 的仓库,是一套大疆固件逆向工具集:拿到一个 dji_sy…

作者头像 李华
网站建设 2026/8/22 23:19:16

HMCL启动器:多版本与模组玩家的实用指南

HMCL启动器:多版本与模组玩家的实用指南 【免费下载链接】HMCL A Minecraft Launcher which is multi-functional, cross-platform and popular 项目地址: https://gitcode.com/gh_mirrors/hm/HMCL HMCL启动器(Hello Minecraft! Launcher&#xf…

作者头像 李华
网站建设 2026/8/22 23:08:58

2002-2025.7全球ESG数据资源包

数据介绍 覆盖全球企业ESG信息,提供CSV格式原始数据文件、指标说明PDF,及5份完整的官方说明手册(Manual),完整数据维度:含ESG综合评分、环境(E)/社会(S)/治理…

作者头像 李华
网站建设 2026/8/22 23:08:38

整体充磁:一种被规模化「逼」出来的工艺路线

导读:永磁转子做了几十年,为什么最近十年才开始谈整体充磁?这篇文章从工艺逻辑出发,讲清楚整体充磁解决了什么问题、在什么条件下划算、目前还有哪些工程难题没解完。一、「先充后装」为什么开始不够用了传统永磁转子制造的流程很…

作者头像 李华