Skip to content

Commit 134a719

Browse files
Merge pull request #2 from dream-sports-labs/feat/aerospike-integration
Feat/aerospike integration
2 parents 1e50060 + c7fc6ba commit 134a719

File tree

11 files changed

+763
-0
lines changed

11 files changed

+763
-0
lines changed
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Vert.x Aerospike Client Instrumentation
2+
3+
This module provides OpenTelemetry instrumentation for Aerospike database operations in Vert.x applications.
4+
5+
## Overview
6+
7+
This instrumentation automatically creates spans for Aerospike database operations (GET, PUT, DELETE, etc.) with relevant attributes following OpenTelemetry semantic conventions for database clients.
8+
9+
## Status
10+
11+
⚠️ **This is a template/starter module** - It requires customization based on your actual Aerospike client implementation with Vert.x.
12+
13+
## Setup Required
14+
15+
### 1. Update Dependencies
16+
17+
In `build.gradle.kts`, update the Aerospike client dependency to match your actual library:
18+
19+
```kotlin
20+
library("io.vertx:vertx-aerospike-client:3.9.0") // If such library exists
21+
// OR
22+
library("com.aerospike:aerospike-client:5.0.0") // Standard Aerospike client
23+
```
24+
25+
### 2. Customize Type Matcher
26+
27+
In `AerospikeClientInstrumentation.java`, update the `typeMatcher()` to match your actual client class:
28+
29+
```java
30+
@Override
31+
public ElementMatcher<TypeDescription> typeMatcher() {
32+
// Replace with actual class name
33+
return named("your.actual.aerospike.Client");
34+
}
35+
```
36+
37+
### 3. Adjust Method Matchers
38+
39+
Update the method matchers to match the actual API methods you want to instrument:
40+
41+
```java
42+
transformer.applyAdviceToMethod(
43+
isMethod()
44+
.and(named("get")) // Match your actual method names
45+
.and(takesArgument(0, ...)), // Match actual parameter types
46+
...
47+
);
48+
```
49+
50+
### 4. Extract Request Metadata
51+
52+
In `AerospikeClientInstrumentation.createRequest()`, implement actual metadata extraction:
53+
54+
```java
55+
private static AerospikeRequest createRequest(String operation, Object key) {
56+
// Extract namespace, set, host, port from actual Aerospike Key/Client
57+
if (key instanceof com.aerospike.client.Key) {
58+
com.aerospike.client.Key aerospikeKey = (com.aerospike.client.Key) key;
59+
String namespace = aerospikeKey.namespace;
60+
String setName = aerospikeKey.setName;
61+
// ... extract other fields
62+
}
63+
64+
return new AerospikeRequest(operation, namespace, setName, host, port);
65+
}
66+
```
67+
68+
### 5. Handle Async Operations
69+
70+
If your Aerospike client uses async operations (like Vert.x Future/Promise), you'll need to:
71+
72+
1. Create a handler wrapper (similar to `VertxRedisClientUtil.java` in Redis module)
73+
2. Capture the context at operation start
74+
3. End the span when the Future/Promise completes
75+
76+
Example:
77+
```java
78+
// In onEnter: wrap the callback handler
79+
if (handler != null) {
80+
handler = wrapHandler(handler, request, context, parentContext);
81+
}
82+
```
83+
84+
### 6. Implement Tests
85+
86+
Update `VertxAerospikeClientTest.java`:
87+
88+
1. Add Aerospike Testcontainer setup
89+
2. Create actual Aerospike client instance
90+
3. Perform operations and verify spans
91+
4. Remove `@Disabled` annotation
92+
93+
## Building
94+
95+
```bash
96+
# Compile the module
97+
./gradlew :instrumentation:vertx:vertx-aerospike-client-3.9:javaagent:compileJava
98+
99+
# Run tests (after implementing)
100+
./gradlew :instrumentation:vertx:vertx-aerospike-client-3.9:javaagent:test
101+
102+
# Build the full agent with this instrumentation
103+
./gradlew :javaagent:shadowJar
104+
```
105+
106+
## Debugging
107+
108+
### Enable Debug Logging
109+
110+
Add to your advice code:
111+
112+
```java
113+
System.out.println("[AEROSPIKE-DEBUG] Operation: " + operation +
114+
", TraceId: " + Span.current().getSpanContext().getTraceId());
115+
```
116+
117+
### Run with Debug Agent
118+
119+
```bash
120+
java -javaagent:path/to/opentelemetry-javaagent.jar \
121+
-Dotel.javaagent.debug=true \
122+
-Dotel.traces.exporter=logging \
123+
-jar your-app.jar
124+
```
125+
126+
### Check Bytecode Transformation
127+
128+
```bash
129+
java -javaagent:path/to/opentelemetry-javaagent.jar \
130+
-Dnet.bytebuddy.dump=/tmp/bytebuddy-dump \
131+
-jar your-app.jar
132+
```
133+
134+
Then inspect `/tmp/bytebuddy-dump/` for transformed classes.
135+
136+
## Module Structure
137+
138+
```
139+
vertx-aerospike-client-3.9/
140+
├── metadata.yaml # Module description
141+
├── README.md # This file
142+
└── javaagent/
143+
├── build.gradle.kts # Build configuration
144+
└── src/
145+
├── main/java/.../aerospike/
146+
│ ├── VertxAerospikeClientInstrumentationModule.java # Entry point
147+
│ ├── AerospikeClientInstrumentation.java # Bytecode advice
148+
│ ├── AerospikeRequest.java # Request model
149+
│ ├── AerospikeAttributesGetter.java # DB attributes
150+
│ ├── AerospikeNetAttributesGetter.java # Network attributes
151+
│ └── AerospikeSingletons.java # Instrumenter setup
152+
└── test/java/.../aerospike/
153+
└── VertxAerospikeClientTest.java # Tests (TODO: implement)
154+
```
155+
156+
## Span Attributes
157+
158+
The instrumentation adds the following attributes to spans:
159+
160+
- `db.system`: "aerospike"
161+
- `db.operation.name`: Operation name (GET, PUT, DELETE, etc.)
162+
- `db.query.text`: Composed query text (e.g., "GET namespace.set")
163+
- `db.namespace`: Aerospike namespace
164+
- `db.collection.name`: Aerospike set name
165+
- `server.address`: Server hostname
166+
- `server.port`: Server port
167+
- `network.peer.address`: Peer IP address
168+
- `network.peer.port`: Peer port
169+
170+
## References
171+
172+
- [OpenTelemetry Java Instrumentation Docs](https://github.com/open-telemetry/opentelemetry-java-instrumentation)
173+
- [Writing Instrumentation Module Guide](../../docs/contributing/writing-instrumentation-module.md)
174+
- [Vert.x Redis Client Instrumentation](../vertx-redis-client-3.9/) (reference implementation)
175+
- [Aerospike Java Client](https://github.com/aerospike/aerospike-client-java)
176+
177+
## Next Steps
178+
179+
1. ✅ Basic module structure created
180+
2. ⚠️ Update dependencies to match actual Aerospike client library
181+
3. ⚠️ Customize type and method matchers for your API
182+
4. ⚠️ Implement metadata extraction from Key/Client objects
183+
5. ⚠️ Handle async operations if needed
184+
6. ⚠️ Implement and enable tests
185+
7. ⚠️ Test with real application
186+
8. ⚠️ Add VirtualField for connection info if needed
187+
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("com.aerospike")
8+
module.set("aerospike-client")
9+
versions.set("[4.0.0,)")
10+
assertInverse.set(true)
11+
}
12+
}
13+
14+
dependencies {
15+
// Aerospike client as a LIBRARY (this is what we're instrumenting)
16+
library("com.aerospike:aerospike-client:4.4.18")
17+
18+
// Vert.x for async patterns
19+
library("io.vertx:vertx-core:3.9.0")
20+
compileOnly("io.vertx:vertx-codegen:3.9.0")
21+
22+
testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent"))
23+
24+
testLibrary("io.vertx:vertx-codegen:3.9.0")
25+
testLibrary("io.vertx:vertx-core:3.9.0")
26+
testLibrary("com.aerospike:aerospike-client:4.4.18")
27+
}
28+
29+
val collectMetadata = findProperty("collectMetadata")?.toString() ?: "false"
30+
31+
tasks {
32+
withType<Test>().configureEach {
33+
usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service)
34+
systemProperty("collectMetadata", collectMetadata)
35+
}
36+
37+
val testStableSemconv by registering(Test::class) {
38+
testClassesDirs = sourceSets.test.get().output.classesDirs
39+
classpath = sourceSets.test.get().runtimeClasspath
40+
jvmArgs("-Dotel.semconv-stability.opt-in=database")
41+
systemProperty("collectMetadata", collectMetadata)
42+
systemProperty("metadataConfig", "otel.semconv-stability.opt-in=database")
43+
}
44+
45+
check {
46+
dependsOn(testStableSemconv)
47+
}
48+
}
49+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.vertx.v3_9.aerospike;
7+
8+
import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientAttributesGetter;
9+
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
10+
import javax.annotation.Nullable;
11+
12+
public enum AerospikeAttributesGetter
13+
implements DbClientAttributesGetter<AerospikeRequest, Void> {
14+
INSTANCE;
15+
16+
@Override
17+
public String getDbSystem(AerospikeRequest request) {
18+
return "aerospike";
19+
}
20+
21+
@Deprecated
22+
@Override
23+
@Nullable
24+
public String getUser(AerospikeRequest request) {
25+
return request.getUser();
26+
}
27+
28+
@Override
29+
@Nullable
30+
public String getDbNamespace(AerospikeRequest request) {
31+
if (SemconvStability.emitStableDatabaseSemconv()) {
32+
return request.getDbNamespace();
33+
}
34+
return null;
35+
}
36+
37+
@Deprecated
38+
@Override
39+
@Nullable
40+
public String getConnectionString(AerospikeRequest request) {
41+
return request.getConnectionString();
42+
}
43+
44+
@Override
45+
@Nullable
46+
public String getDbQueryText(AerospikeRequest request) {
47+
// Aerospike doesn't have query text like SQL/Redis
48+
// We can compose operation + namespace + set for better visibility
49+
StringBuilder queryText = new StringBuilder(request.getOperation());
50+
if (request.getNamespace() != null) {
51+
queryText.append(" ").append(request.getNamespace());
52+
}
53+
if (request.getSetName() != null) {
54+
queryText.append(".").append(request.getSetName());
55+
}
56+
return queryText.toString();
57+
}
58+
59+
@Nullable
60+
@Override
61+
public String getDbOperationName(AerospikeRequest request) {
62+
return request.getOperation();
63+
}
64+
}
65+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.vertx.v3_9.aerospike;
7+
8+
import javax.annotation.Nullable;
9+
10+
/**
11+
* Helper class for Aerospike instrumentation.
12+
* Separated to avoid muzzle scanning TypeInstrumentation framework classes.
13+
*/
14+
public final class AerospikeInstrumentationHelper {
15+
16+
@Nullable
17+
public static AerospikeRequest createRequest(String operation, Object key) {
18+
if (key == null) {
19+
return null;
20+
}
21+
22+
String namespace = null;
23+
String setName = null;
24+
25+
if (key instanceof com.aerospike.client.Key) {
26+
com.aerospike.client.Key aerospikeKey = (com.aerospike.client.Key) key;
27+
namespace = aerospikeKey.namespace;
28+
setName = aerospikeKey.setName;
29+
}
30+
31+
return new AerospikeRequest(operation, namespace, setName, null, null);
32+
}
33+
34+
private AerospikeInstrumentationHelper() {}
35+
}
36+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.vertx.v3_9.aerospike;
7+
8+
import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesGetter;
9+
import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesGetter;
10+
import javax.annotation.Nullable;
11+
12+
enum AerospikeNetAttributesGetter
13+
implements
14+
ServerAttributesGetter<AerospikeRequest>,
15+
NetworkAttributesGetter<AerospikeRequest, Void> {
16+
INSTANCE;
17+
18+
@Nullable
19+
@Override
20+
public String getServerAddress(AerospikeRequest request) {
21+
return request.getHost();
22+
}
23+
24+
@Nullable
25+
@Override
26+
public Integer getServerPort(AerospikeRequest request) {
27+
return request.getPort();
28+
}
29+
30+
@Override
31+
@Nullable
32+
public String getNetworkPeerAddress(AerospikeRequest request, @Nullable Void unused) {
33+
return request.getPeerAddress();
34+
}
35+
36+
@Override
37+
@Nullable
38+
public Integer getNetworkPeerPort(AerospikeRequest request, @Nullable Void unused) {
39+
return request.getPeerPort();
40+
}
41+
}
42+

0 commit comments

Comments
 (0)