Skip to content

Commit 28f1a11

Browse files
committed
Add MongoDB 5.x plugin and extend 4.x to cover 5.0-5.1
MongoDB 5.2+ replaced MongoClientDelegate with package-private MongoClusterImpl. New mongodb-5.x-plugin module with: - MongoClusterImplInstrumentation: intercept constructor (Cluster at arg[1]) and getOperationExecutor() to propagate remotePeer - MongoClusterOperationExecutorInstrumentation: intercept inner class OperationExecutorImpl constructor (enclosing instance at synthetic arg[0]) and execute() methods (reuses 4.x MongoDBOperationExecutorInterceptor) Key challenge: OperationExecutorImpl is created inside MongoClusterImpl's constructor before onConstruct fires. Solved by calling getOperationExecutor() via setAccessible reflection in onConstruct to set peer on the stored executor. TODO: Add @InternalAccessor annotation to core framework to allow plugin helper classes in target library packages, bypassing checkstyle package rules. Locally verified: 5.0.1, 5.1.4 (4.x plugin), 5.2.0, 5.5.1 (5.x plugin) all passed.
1 parent bb404a2 commit 28f1a11

20 files changed

Lines changed: 1011 additions & 1 deletion

File tree

.github/workflows/plugins-test.1.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ jobs:
9191
- lettuce-webflux-5x-scenario
9292
- mongodb-3.x-scenario
9393
- mongodb-4.x-scenario
94+
- mongodb-5.x-scenario
9495
- netty-socketio-scenario
9596
- postgresql-above9.4.1207-scenario
9697
- mssql-jtds-scenario
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Licensed to the Apache Software Foundation (ASF) under one or more
4+
~ contributor license agreements. See the NOTICE file distributed with
5+
~ this work for additional information regarding copyright ownership.
6+
~ The ASF licenses this file to You under the Apache License, Version 2.0
7+
~ (the "License"); you may not use this file except in compliance with
8+
~ the License. You may obtain a copy of the License at
9+
~
10+
~ http://www.apache.org/licenses/LICENSE-2.0
11+
~
12+
~ Unless required by applicable law or agreed to in writing, software
13+
~ distributed under the License is distributed on an "AS IS" BASIS,
14+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
~ See the License for the specific language governing permissions and
16+
~ limitations under the License.
17+
~
18+
-->
19+
20+
<project xmlns="http://maven.apache.org/POM/4.0.0"
21+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
22+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
23+
<modelVersion>4.0.0</modelVersion>
24+
25+
<parent>
26+
<artifactId>apm-sdk-plugin</artifactId>
27+
<groupId>org.apache.skywalking</groupId>
28+
<version>9.7.0-SNAPSHOT</version>
29+
</parent>
30+
31+
<artifactId>apm-mongodb-5.x-plugin</artifactId>
32+
<packaging>jar</packaging>
33+
34+
<name>mongodb-5.x-plugin</name>
35+
36+
<dependencies>
37+
<dependency>
38+
<groupId>org.mongodb</groupId>
39+
<artifactId>mongodb-driver-sync</artifactId>
40+
<version>5.2.0</version>
41+
<scope>provided</scope>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.apache.skywalking</groupId>
45+
<artifactId>apm-mongodb-4.x-plugin</artifactId>
46+
<version>${project.version}</version>
47+
<scope>provided</scope>
48+
</dependency>
49+
</dependencies>
50+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package org.apache.skywalking.apm.plugin.mongodb.v5.define;
20+
21+
import net.bytebuddy.description.method.MethodDescription;
22+
import net.bytebuddy.matcher.ElementMatcher;
23+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
24+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
25+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
26+
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
27+
28+
import static net.bytebuddy.matcher.ElementMatchers.named;
29+
import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType;
30+
import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
31+
32+
/**
33+
* Enhance {@code com.mongodb.client.internal.MongoClusterImpl} which replaces
34+
* {@code MongoClientDelegate} in MongoDB driver 5.2+.
35+
* Extract remotePeer from Cluster (constructor arg[1]) and store in dynamic field.
36+
*/
37+
public class MongoClusterImplInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
38+
39+
private static final String ENHANCE_CLASS = "com.mongodb.client.internal.MongoClusterImpl";
40+
41+
private static final String CONSTRUCTOR_INTERCEPTOR =
42+
"org.apache.skywalking.apm.plugin.mongodb.v5.interceptor.MongoClusterImplConstructorInterceptor";
43+
44+
@Override
45+
protected String[] witnessClasses() {
46+
return new String[] {ENHANCE_CLASS};
47+
}
48+
49+
@Override
50+
protected ClassMatch enhanceClass() {
51+
return byName(ENHANCE_CLASS);
52+
}
53+
54+
@Override
55+
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
56+
return new ConstructorInterceptPoint[] {
57+
new ConstructorInterceptPoint() {
58+
@Override
59+
public ElementMatcher<MethodDescription> getConstructorMatcher() {
60+
return takesArgumentWithType(1, "com.mongodb.internal.connection.Cluster");
61+
}
62+
63+
@Override
64+
public String getConstructorInterceptor() {
65+
return CONSTRUCTOR_INTERCEPTOR;
66+
}
67+
}
68+
};
69+
}
70+
71+
@Override
72+
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
73+
return new InstanceMethodsInterceptPoint[] {
74+
new InstanceMethodsInterceptPoint() {
75+
@Override
76+
public ElementMatcher<MethodDescription> getMethodsMatcher() {
77+
return named("getOperationExecutor");
78+
}
79+
80+
@Override
81+
public String getMethodsInterceptor() {
82+
return CONSTRUCTOR_INTERCEPTOR;
83+
}
84+
85+
@Override
86+
public boolean isOverrideArgs() {
87+
return false;
88+
}
89+
}
90+
};
91+
}
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package org.apache.skywalking.apm.plugin.mongodb.v5.define;
20+
21+
import net.bytebuddy.description.method.MethodDescription;
22+
import net.bytebuddy.matcher.ElementMatcher;
23+
import net.bytebuddy.matcher.ElementMatchers;
24+
import org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch;
25+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
26+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
27+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
28+
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
29+
import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch;
30+
31+
/**
32+
* Enhance {@code MongoClusterImpl$OperationExecutorImpl} in MongoDB driver 5.2+.
33+
* <p>
34+
* Constructor interception: propagate remotePeer from enclosing MongoClusterImpl
35+
* (synthetic arg[0] for non-static inner class).
36+
* <p>
37+
* Method interception: create exit spans on execute() calls.
38+
* Reuses the 4.x MongoDBOperationExecutorInterceptor.
39+
*/
40+
public class MongoClusterOperationExecutorInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
41+
42+
private static final String ENHANCE_CLASS =
43+
"com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl";
44+
45+
private static final String CONSTRUCTOR_INTERCEPTOR =
46+
"org.apache.skywalking.apm.plugin.mongodb.v5.interceptor.OperationExecutorImplConstructorInterceptor";
47+
48+
private static final String EXECUTE_INTERCEPTOR =
49+
"org.apache.skywalking.apm.plugin.mongodb.v4.interceptor.MongoDBOperationExecutorInterceptor";
50+
51+
private static final String METHOD_NAME = "execute";
52+
53+
private static final String ARGUMENT_TYPE = "com.mongodb.client.ClientSession";
54+
55+
@Override
56+
protected String[] witnessClasses() {
57+
return new String[] {"com.mongodb.client.internal.MongoClusterImpl"};
58+
}
59+
60+
@Override
61+
protected ClassMatch enhanceClass() {
62+
return NameMatch.byName(ENHANCE_CLASS);
63+
}
64+
65+
@Override
66+
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
67+
return new ConstructorInterceptPoint[] {
68+
new ConstructorInterceptPoint() {
69+
@Override
70+
public ElementMatcher<MethodDescription> getConstructorMatcher() {
71+
return ElementMatchers.any();
72+
}
73+
74+
@Override
75+
public String getConstructorInterceptor() {
76+
return CONSTRUCTOR_INTERCEPTOR;
77+
}
78+
}
79+
};
80+
}
81+
82+
@Override
83+
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
84+
return new InstanceMethodsInterceptPoint[] {
85+
new InstanceMethodsInterceptPoint() {
86+
@Override
87+
public ElementMatcher<MethodDescription> getMethodsMatcher() {
88+
return ElementMatchers
89+
.named(METHOD_NAME)
90+
.and(ArgumentTypeNameMatch.takesArgumentWithType(2, ARGUMENT_TYPE))
91+
.or(ElementMatchers.<MethodDescription>named(METHOD_NAME)
92+
.and(ArgumentTypeNameMatch.takesArgumentWithType(3, ARGUMENT_TYPE)));
93+
}
94+
95+
@Override
96+
public String getMethodsInterceptor() {
97+
return EXECUTE_INTERCEPTOR;
98+
}
99+
100+
@Override
101+
public boolean isOverrideArgs() {
102+
return false;
103+
}
104+
}
105+
};
106+
}
107+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package org.apache.skywalking.apm.plugin.mongodb.v5.interceptor;
20+
21+
import com.mongodb.internal.connection.Cluster;
22+
import org.apache.skywalking.apm.agent.core.logging.api.ILog;
23+
import org.apache.skywalking.apm.agent.core.logging.api.LogManager;
24+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
25+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
26+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
27+
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
28+
import org.apache.skywalking.apm.plugin.mongodb.v4.support.MongoRemotePeerHelper;
29+
30+
import java.lang.reflect.Method;
31+
32+
/**
33+
* Intercept {@code MongoClusterImpl} constructor and {@code getOperationExecutor()}.
34+
* <p>
35+
* Constructor: extract remotePeer from Cluster (arg[1]) and store in dynamic field.
36+
* getOperationExecutor(): pass remotePeer to the returned OperationExecutor.
37+
*/
38+
public class MongoClusterImplConstructorInterceptor
39+
implements InstanceConstructorInterceptor, InstanceMethodsAroundInterceptor {
40+
41+
private static final ILog LOGGER = LogManager.getLogger(MongoClusterImplConstructorInterceptor.class);
42+
43+
@Override
44+
public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
45+
Cluster cluster = (Cluster) allArguments[1];
46+
String remotePeer = MongoRemotePeerHelper.getRemotePeer(cluster);
47+
objInst.setSkyWalkingDynamicField(remotePeer);
48+
49+
// The OperationExecutorImpl is created INSIDE this constructor (before onConstruct fires),
50+
// so its constructor interceptor couldn't read the peer yet. Set it now.
51+
// MongoClusterImpl is package-private, access getOperationExecutor via reflection.
52+
try {
53+
java.lang.reflect.Method getExecutor = objInst.getClass().getMethod("getOperationExecutor");
54+
getExecutor.setAccessible(true);
55+
Object executor = getExecutor.invoke(objInst);
56+
if (executor instanceof EnhancedInstance) {
57+
((EnhancedInstance) executor).setSkyWalkingDynamicField(remotePeer);
58+
}
59+
} catch (Exception e) {
60+
LOGGER.warn("Failed to set remotePeer on OperationExecutor", e);
61+
}
62+
}
63+
64+
@Override
65+
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments,
66+
Class<?>[] argumentsTypes, MethodInterceptResult result) {
67+
}
68+
69+
@Override
70+
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments,
71+
Class<?>[] argumentsTypes, Object ret) {
72+
if (ret instanceof EnhancedInstance) {
73+
EnhancedInstance retInstance = (EnhancedInstance) ret;
74+
String remotePeer = (String) objInst.getSkyWalkingDynamicField();
75+
if (LOGGER.isDebugEnable()) {
76+
LOGGER.debug("Mark OperationExecutor remotePeer: {}", remotePeer);
77+
}
78+
retInstance.setSkyWalkingDynamicField(remotePeer);
79+
}
80+
return ret;
81+
}
82+
83+
@Override
84+
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
85+
Class<?>[] argumentsTypes, Throwable t) {
86+
}
87+
}

0 commit comments

Comments
 (0)