From 1319b5055e8321afd531f43ce848e577f2ad0204 Mon Sep 17 00:00:00 2001 From: wyhhai <2509326799@qq.com> Date: Sun, 20 Sep 2026 11:36:16 +0800 Subject: [PATCH] =?UTF-8?q?fix(java):=20=E9=83=A8=E7=BD=B2=E7=BA=A7?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E4=BB=8E=E5=85=A8=E5=B1=80=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=EF=BC=8C=E4=B8=8D=E5=86=8D=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E6=9C=AC=E5=9C=B0=20MDC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 请求线程打日志时 service/env/instance 曾因 init() 只写启动线程 MDC 而整条缺失; ObsJsonProvider 改为直接读 ObsLogging 全局静态访问器(未 init 回退契约默认), community 保持 MDC 请求级覆盖 > 部署默认语义。补新线程回归测试。 --- .../obssdk/log/ObsJsonProvider.java | 32 +++++++++----- .../opensourceways/obssdk/log/ObsLogging.java | 38 ++++++++++------ .../obssdk/ObsJsonProviderTest.java | 43 +++++++++++++++++++ .../opensourceways/obssdk/ObsLoggingTest.java | 25 +++++++++++ 4 files changed, 115 insertions(+), 23 deletions(-) diff --git a/java/src/main/java/io/opensourceways/obssdk/log/ObsJsonProvider.java b/java/src/main/java/io/opensourceways/obssdk/log/ObsJsonProvider.java index 2d6e843..7ae715c 100644 --- a/java/src/main/java/io/opensourceways/obssdk/log/ObsJsonProvider.java +++ b/java/src/main/java/io/opensourceways/obssdk/log/ObsJsonProvider.java @@ -31,9 +31,11 @@ * *
字段顺序:{@code time / level / msg / service / env / instance / community / * request_id / trace_id / span_id / logger / error}。其中 - * {@code service / env / instance / community} 来自 {@link ObsLogging#init} 写入的 MDC, - * 其余请求级字段由中间件经 {@link ObsLogging#enrich} 写入,空值一律省略 - * ({@code trace_id} / {@code span_id} 为二期预留,首期通常不出现)。 + * {@code service / env / instance / community} 是部署级字段,来自全局配置 + * ({@link ObsLogging} 静态访问器,不依赖线程本地 MDC),恒非空;{@code community} + * 在请求线程上取 MDC 请求级覆盖,未覆盖回退部署默认。其余请求级字段 + * ({@code request_id / trace_id / span_id})由中间件经 {@link ObsLogging#enrich} + * 写入 MDC,空值一律省略({@code trace_id} / {@code span_id} 为二期预留,首期通常不出现)。 * *
业务字段不属于本 provider 的职责:接入方可在本 provider 之后追加
* {@code community 双层注入与其它语言 SDK 一致:部署级默认来自 {@code OBS_*}/Config,
- * 请求级由中间件在可信判定点解析后经 {@link RequestContext#push} 写入,
- * 本助手在取数时用请求级覆盖默认(同 metrics 的 resolve 语义)。
+ * 部署级字段({@code service / env / instance / community} 默认值)本质是全局静态配置
+ * (来源 {@code OBS_*} / Config),不随请求线程变化,因此不走线程本地 MDC ——
+ * {@link ObsJsonProvider} 直接从本类的全局静态访问器取数。MDC 只承载请求级字段:
+ * {@code community} 请求覆盖(可信判定点解析后经 {@link RequestContext#push} 写入)
+ * 优先于部署默认,{@code request_id} 由中间件注入。
*/
public final class ObsLogging {
@@ -38,10 +40,6 @@ private ObsLogging() {
/** 全局初始化一次:登记部署级默认字段(通常服务启动时调用)。 */
public static void init(ObsSdkConfig config) {
cfg = config;
- MDC.put(MDC_SERVICE, nvl(config.service()));
- MDC.put(MDC_ENV, nvl(config.envName()));
- MDC.put(MDC_INSTANCE, nvl(config.instance()));
- MDC.put(MDC_COMMUNITY, nvl(config.community()));
}
/**
@@ -90,13 +88,27 @@ public static void clearRequestScope() {
MDC.put(MDC_COMMUNITY, deploymentCommunity());
}
- /** 部署级默认 community;{@code init} 未调用时退到契约默认(见 internal/Env)。 */
- private static String deploymentCommunity() {
+ /** 部署级 service;{@code init} 未调用时回退契约默认(见 internal/Env)。 */
+ public static String deploymentService() {
ObsSdkConfig current = cfg;
- return current != null && current.community() != null ? current.community() : Env.DEFAULT_VALUE;
+ return current != null && current.service() != null ? current.service() : Env.DEFAULT_VALUE;
}
- private static String nvl(String v) {
- return v == null ? "" : v;
+ /** 部署级 env;{@code init} 未调用时回退契约默认(见 internal/Env)。 */
+ public static String deploymentEnv() {
+ ObsSdkConfig current = cfg;
+ return current != null && current.envName() != null ? current.envName() : Env.DEFAULT_VALUE;
+ }
+
+ /** 部署级 instance;{@code init} 未调用时回退契约默认(见 internal/Env)。 */
+ public static String deploymentInstance() {
+ ObsSdkConfig current = cfg;
+ return current != null && current.instance() != null ? current.instance() : Env.DEFAULT_VALUE;
+ }
+
+ /** 部署级默认 community;{@code init} 未调用时回退契约默认(见 internal/Env)。 */
+ public static String deploymentCommunity() {
+ ObsSdkConfig current = cfg;
+ return current != null && current.community() != null ? current.community() : Env.DEFAULT_VALUE;
}
}
diff --git a/java/src/test/java/io/opensourceways/obssdk/ObsJsonProviderTest.java b/java/src/test/java/io/opensourceways/obssdk/ObsJsonProviderTest.java
index ccee651..658a198 100644
--- a/java/src/test/java/io/opensourceways/obssdk/ObsJsonProviderTest.java
+++ b/java/src/test/java/io/opensourceways/obssdk/ObsJsonProviderTest.java
@@ -221,6 +221,49 @@ void tearDown() {
assertFalse(lastLine().contains("\"error\""), lastLine());
}
+ /**
+ * 缺陷回归:部署级字段曾只写入 {@code init()} 所在线程的 MDC,web 容器请求线程
+ * 拿不到 service/env/instance,导致整条字段消失。修复后部署级字段来自全局配置,
+ * 请求线程即使只写请求级 MDC 也完整输出。
+ */
+ @Test
+ void 请求线程模拟_部署级字段来自全局配置而非线程MDC() throws Exception {
+ Thread request = new Thread(() -> {
+ MDC.put(ObsLogging.MDC_COMMUNITY, "mindspore");
+ MDC.put(ObsLogging.MDC_REQUEST_ID, "req-9");
+ LoggerFactory.getLogger("com.x.ReviewSvc").info("job done");
+ });
+ request.start();
+ request.join();
+
+ String json = lastLine();
+ assertTrue(json.contains("\"service\":\"review\""), json);
+ assertTrue(json.contains("\"env\":\"test\""), json);
+ assertTrue(json.contains("\"instance\":\"pod-1\""), json);
+ // community 保持「MDC 请求级覆盖 > 部署默认」语义
+ assertTrue(json.contains("\"community\":\"mindspore\""), json);
+ assertTrue(json.contains("\"request_id\":\"req-9\""), json);
+ }
+
+ /**
+ * 异步/定时线程不打请求级 MDC 时,部署级字段(含 community)也必须输出部署默认,
+ * 而不是整条从 JSON 里消失(契约要求四个部署级字段恒非空)。
+ */
+ @Test
+ void 新线程无请求级MDC时_部署级字段回退部署默认() throws Exception {
+ Thread async = new Thread(() ->
+ LoggerFactory.getLogger("com.x.ReviewSvc").info("cron tick"));
+ async.start();
+ async.join();
+
+ String json = lastLine();
+ assertTrue(json.contains("\"service\":\"review\""), json);
+ assertTrue(json.contains("\"env\":\"test\""), json);
+ assertTrue(json.contains("\"instance\":\"pod-1\""), json);
+ assertTrue(json.contains("\"community\":\"openEuler\""), json);
+ assertFalse(json.contains("request_id"), json);
+ }
+
private String line(int index) {
String[] lines = captured.toString(StandardCharsets.UTF_8).split("\n");
assertTrue(lines.length > index, "日志行数不足:" + captured);
diff --git a/java/src/test/java/io/opensourceways/obssdk/ObsLoggingTest.java b/java/src/test/java/io/opensourceways/obssdk/ObsLoggingTest.java
index 68854f7..32bf41a 100644
--- a/java/src/test/java/io/opensourceways/obssdk/ObsLoggingTest.java
+++ b/java/src/test/java/io/opensourceways/obssdk/ObsLoggingTest.java
@@ -82,4 +82,29 @@ void tearDown() {
"应复位为部署默认,而不是残留上一个请求的覆盖值");
}
}
+
+ @Test
+ void 部署级静态访问器返回init登记的值() {
+ assertEquals("review", ObsLogging.deploymentService());
+ assertEquals("test", ObsLogging.deploymentEnv());
+ assertEquals("pod-1", ObsLogging.deploymentInstance());
+ assertEquals("openeuler", ObsLogging.deploymentCommunity());
+ }
+
+ /**
+ * 缺陷回归:部署级字段一旦只存在于线程本地 MDC 就会随线程消失;访问器是全局静态
+ * 取值,{@code init} 未调用时仍须回退契约默认("unknown"),保证 provider 恒能
+ * 输出非空部署级字段。
+ */
+ @Test
+ void 未init时部署级访问器回退契约默认() throws Exception {
+ java.lang.reflect.Field cfgField = ObsLogging.class.getDeclaredField("cfg");
+ cfgField.setAccessible(true);
+ cfgField.set(null, null);
+
+ assertEquals("unknown", ObsLogging.deploymentService());
+ assertEquals("unknown", ObsLogging.deploymentEnv());
+ assertEquals("unknown", ObsLogging.deploymentInstance());
+ assertEquals("unknown", ObsLogging.deploymentCommunity());
+ }
}