blob: 90e3bf0f6c41faa3c1acc44e75235836f94ec050 [file] [log] [blame] [view]
# CLAUDE.md - AI Assistant Guide for Apache SkyWalking Java Agent
This file provides guidance for AI assistants working with the Apache SkyWalking Java Agent codebase.
## Project Overview
Apache SkyWalking Java Agent is a Java-based APM (Application Performance Monitoring) agent designed for microservices, cloud-native, and container-based architectures. It provides automatic instrumentation for distributed tracing, performance metrics collection, and context propagation across service boundaries using bytecode manipulation via ByteBuddy.
## Repository Structure
```
skywalking-java/
├── apm-commons/ # Shared utilities and libraries
│ ├── apm-datacarrier/ # Data buffering and transport
│ └── apm-util/ # Common utilities
├── apm-protocol/ # Protocol definitions
│ └── apm-network/ # gRPC protocol (submodule: skywalking-data-collect-protocol)
├── apm-sniffer/ # Core agent and plugins (MAIN MODULE)
│ ├── apm-agent/ # Main agent bootstrap and premain entry
│ ├── apm-agent-core/ # Core agent logic, instrumentation engine
│ ├── apm-sdk-plugin/ # Standard SDK plugins (70+ plugins)
│ ├── bootstrap-plugins/ # Bootstrap-level plugins (JDK-level)
│ ├── optional-plugins/ # Optional framework plugins
│ ├── optional-reporter-plugins/ # Reporter plugins (Kafka, etc.)
│ ├── apm-toolkit-activation/ # Toolkit activations
│ ├── apm-test-tools/ # Testing utilities
│ ├── bytebuddy-patch/ # ByteBuddy patches
│ └── config/ # Default agent configurations
├── apm-application-toolkit/ # Public API for applications
│ ├── apm-toolkit-trace/ # Tracing API
│ ├── apm-toolkit-log4j-1.x/ # Log4j 1.x integration
│ ├── apm-toolkit-log4j-2.x/ # Log4j 2.x integration
│ ├── apm-toolkit-logback-1.x/ # Logback integration
│ ├── apm-toolkit-meter/ # Meter API
│ └── apm-toolkit-opentracing/ # OpenTracing API
├── apm-checkstyle/ # Code style configuration
│ ├── checkStyle.xml # Checkstyle rules
│ └── importControl.xml # Import control rules
├── test/ # Testing infrastructure
│ ├── plugin/ # Plugin E2E tests
│ │ ├── scenarios/ # Test scenarios (100+ scenarios)
│ │ ├── agent-test-tools/ # Mock collector, test utilities
│ │ ├── runner-helper/ # Test runner
│ │ └── containers/ # Docker test containers
│ └── e2e/ # End-to-end tests
├── docs/ # Documentation
├── tools/ # Build and utility tools
├── skywalking-agent/ # Built agent distribution output
├── changes/ # Changelog
└── dist-material/ # Distribution materials
```
## Build System
### Prerequisites
- JDK 8, 11, 17, 21, or 25
- Maven 3.6+
- Git (with submodule support)
### Common Build Commands
```bash
# Clone with submodules
git clone --recurse-submodules https://github.com/apache/skywalking-java.git
# Or initialize submodules after clone
git submodule init && git submodule update
# Full build with tests
./mvnw clean install
# Build without tests (recommended for development)
./mvnw clean package -Dmaven.test.skip=true
# CI build with javadoc verification
./mvnw clean verify install javadoc:javadoc
# Run checkstyle only
./mvnw checkstyle:check
# Build with submodule update
./mvnw clean package -Pall
# Docker build
make build
make docker
```
### Maven Profiles
- `all`: Includes git submodule update for protocol definitions
### Key Build Properties
- ByteBuddy: 1.17.6 (bytecode manipulation)
- gRPC: 1.74.0 (communication protocol)
- Netty: 4.1.124.Final (network framework)
- Protobuf: 3.25.5 (protocol buffers)
- Lombok: 1.18.42 (annotation processing)
## Architecture & Key Concepts
### Agent Architecture
The agent uses ByteBuddy for bytecode manipulation at runtime:
1. **Premain Entry**: `apm-agent/` contains the agent bootstrap via Java's `-javaagent` mechanism
2. **Instrumentation Engine**: `apm-agent-core/` handles class transformation and plugin loading
3. **Plugins**: Define which classes/methods to intercept and how to collect telemetry
### Plugin Categories
**1. SDK Plugins** (`apm-sniffer/apm-sdk-plugin/`)
- Framework-specific instrumentations (70+ plugins)
- Examples: grpc-1.x, spring, dubbo, mybatis, mongodb, redis, etc.
- Pattern: One directory per library/framework version
**2. Bootstrap Plugins** (`apm-sniffer/bootstrap-plugins/`)
- Load at JVM bootstrap phase for JDK-level instrumentation
- Examples: jdk-threading, jdk-http, jdk-httpclient, jdk-virtual-thread-executor
**3. Optional Plugins** (`apm-sniffer/optional-plugins/`)
- Not included by default, user must copy to plugins directory
**4. Optional Reporter Plugins** (`apm-sniffer/optional-reporter-plugins/`)
- Alternative data collection backends (e.g., Kafka)
### Plugin Instrumentation APIs (v1 vs v2)
The agent provides two instrumentation APIs. **V2 is recommended** for all new plugins; v1 is legacy and should only be used for maintaining existing plugins.
#### V2 API (Recommended)
V2 provides a `MethodInvocationContext` that is shared across all interception phases (`beforeMethod`, `afterMethod`, `handleMethodException`), allowing you to pass data (e.g., spans) between phases.
**Instrumentation class (extends `ClassEnhancePluginDefineV2`):**
```java
public class XxxInstrumentation extends ClassInstanceMethodsEnhancePluginDefineV2 {
@Override
protected ClassMatch enhanceClass() {
return NameMatch.byName("target.class.Name");
}
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
return new ConstructorInterceptPoint[] { ... };
}
@Override
public InstanceMethodsInterceptV2Point[] getInstanceMethodsInterceptV2Points() {
return new InstanceMethodsInterceptV2Point[] {
new InstanceMethodsInterceptV2Point() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named("targetMethod");
}
@Override
public String getMethodsInterceptorV2() {
return "org.apache.skywalking.apm.plugin.xxx.XxxInterceptor";
}
@Override
public boolean isOverrideArgs() {
return false;
}
}
};
}
}
```
**Interceptor class (implements `InstanceMethodsAroundInterceptorV2`):**
```java
public class XxxInterceptor implements InstanceMethodsAroundInterceptorV2 {
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments,
Class<?>[] argumentsTypes, MethodInvocationContext context) {
// Create span and store in context for later use
AbstractSpan span = ContextManager.createLocalSpan("operationName");
context.setContext(span); // Pass to afterMethod/handleMethodException
}
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments,
Class<?>[] argumentsTypes, Object ret, MethodInvocationContext context) {
// Retrieve span from context
AbstractSpan span = (AbstractSpan) context.getContext();
span.asyncFinish();
return ret;
}
@Override
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
Class<?>[] argumentsTypes, Throwable t, MethodInvocationContext context) {
AbstractSpan span = (AbstractSpan) context.getContext();
span.log(t);
}
}
```
**Key V2 classes:**
- `ClassEnhancePluginDefineV2` - Base class for plugins with both instance and static methods
- `ClassInstanceMethodsEnhancePluginDefineV2` - For instance methods only
- `ClassStaticMethodsEnhancePluginDefineV2` - For static methods only
- `InstanceMethodsAroundInterceptorV2` - Interceptor interface with `MethodInvocationContext`
- `StaticMethodsAroundInterceptorV2` - Static method interceptor with context
#### V1 API (Legacy)
V1 uses `MethodInterceptResult` only in `beforeMethod` and has no shared context between phases. **Only use for maintaining existing legacy plugins.**
**Key V1 classes (legacy):**
- `ClassEnhancePluginDefine`
- `ClassInstanceMethodsEnhancePluginDefine`
- `ClassStaticMethodsEnhancePluginDefine`
- `InstanceMethodsAroundInterceptor`
- `StaticMethodsAroundInterceptor`
### Plugin Development Rules
#### Class Matching Restrictions
**CRITICAL: Never use `.class` references in instrumentation definitions:**
```java
// WRONG - will break the agent if ThirdPartyClass doesn't exist
takesArguments(ThirdPartyClass.class)
byName(ThirdPartyClass.class.getName())
// CORRECT - use string literals
takesArguments("com.example.ThirdPartyClass")
byName("com.example.ThirdPartyClass")
```
**ClassMatch options:**
- `byName(String)`: Match by full class name (package + class name) - **preferred**
- `byClassAnnotationMatch`: Match classes with specific annotations (does NOT support inherited annotations)
- `byMethodAnnotationMatch`: Match classes with methods having specific annotations
- `byHierarchyMatch`: Match by parent class/interface - **avoid unless necessary** (performance impact)
#### Witness Classes/Methods
Use witness classes/methods to activate plugins only for specific library versions:
```java
@Override
protected String[] witnessClasses() {
return new String[] { "com.example.VersionSpecificClass" };
}
@Override
protected List<WitnessMethod> witnessMethods() {
return Collections.singletonList(
new WitnessMethod("com.example.SomeClass", ElementMatchers.named("specificMethod"))
);
}
```
#### Bootstrap Instrumentation
For JDK core classes (rt.jar), override `isBootstrapInstrumentation()`:
```java
@Override
public boolean isBootstrapInstrumentation() {
return true;
}
```
**WARNING**: Use bootstrap instrumentation only where absolutely necessary.
#### Plugin Configuration
Use `@PluginConfig` annotation for custom plugin settings:
```java
public class MyPluginConfig {
public static class Plugin {
@PluginConfig(root = MyPluginConfig.class)
public static class MyPlugin {
public static boolean SOME_SETTING = false;
}
}
}
```
Config key becomes: `plugin.myplugin.some_setting`
#### Dependency Management
**Plugin dependencies must use `provided` scope:**
```xml
<dependency>
<groupId>com.example</groupId>
<artifactId>target-library</artifactId>
<version>${version}</version>
<scope>provided</scope>
</dependency>
```
**Agent core dependency policy:**
- New dependencies in agent core are treated with extreme caution
- Prefer using existing imported libraries already in the project
- Prefer JDK standard libraries over third-party libraries
- Plugins should rely on the target application's libraries (provided scope), not bundle them
### Tracing Concepts
#### Span Types
- **EntrySpan**: Service provider/endpoint (HTTP server, MQ consumer)
- **LocalSpan**: Internal method (no remote calls)
- **ExitSpan**: Client call (HTTP client, DB access, MQ producer)
#### SpanLayer (required for EntrySpan/ExitSpan)
- `DB`: Database access
- `RPC_FRAMEWORK`: RPC calls (not ordinary HTTP)
- `HTTP`: HTTP calls
- `MQ`: Message queue
- `UNKNOWN`: Default
#### Context Propagation
- **ContextCarrier**: Cross-process propagation (serialize to headers/attachments)
- **ContextSnapshot**: Cross-thread propagation (in-memory, no serialization)
#### Required Span Attributes
For EntrySpan and ExitSpan, always set:
```java
span.setComponent(ComponentsDefine.YOUR_COMPONENT);
span.setLayer(SpanLayer.HTTP); // or DB, MQ, RPC_FRAMEWORK
```
#### Special Tags for OAP Analysis
| Tag | Purpose |
|-----|---------|
| `http.status_code` | HTTP response code (integer) |
| `db.type` | Database type (e.g., "sql", "redis") |
| `db.statement` | SQL/query statement (enables slow query analysis) |
| `cache.type`, `cache.op`, `cache.cmd`, `cache.key` | Cache metrics |
| `mq.queue`, `mq.topic` | MQ metrics |
### Meter Plugin APIs
For collecting numeric metrics (alternative to tracing):
```java
// Counter
Counter counter = MeterFactory.counter("metric_name")
.tag("key", "value")
.mode(Counter.Mode.INCREMENT)
.build();
counter.increment(1d);
// Gauge
Gauge gauge = MeterFactory.gauge("metric_name", () -> getValue())
.tag("key", "value")
.build();
// Histogram
Histogram histogram = MeterFactory.histogram("metric_name")
.steps(Arrays.asList(1, 5, 10))
.build();
histogram.addValue(3);
```
### Data Flow
1. Agent attaches to JVM via `-javaagent` flag
2. ByteBuddy transforms target classes at load time
3. Interceptors collect span/trace data on method entry/exit
4. Data is buffered via DataCarrier
5. gRPC reporter sends data to OAP backend
## Code Style & Conventions
### Checkstyle Rules (enforced via `apm-checkstyle/checkStyle.xml`)
**Prohibited patterns:**
- No `System.out.println` - use proper logging
- No `@author` tags - ASF projects don't use author annotations
- No Chinese characters in source files
- No tab characters (use 4 spaces)
- No star imports (`import xxx.*`)
- No unused or redundant imports
**Required patterns:**
- `@Override` annotation required for overridden methods
- `equals()` and `hashCode()` must be overridden together
- Apache 2.0 license header on all source files
**Naming conventions:**
- Constants/static variables: `UPPER_CASE_WITH_UNDERSCORES`
- Package names: `org.apache.skywalking.apm.*` or `test.apache.skywalking.apm.*`
- Type names: `PascalCase`
- Local variables/parameters/members: `camelCase`
- Plugin directories: `{framework}-{version}-plugin`
- Instrumentation classes: `*Instrumentation.java`
- Interceptor classes: `*Interceptor.java`
**File limits:**
- Max file length: 3000 lines
### Lombok Usage
Use Lombok annotations for boilerplate code:
- `@Getter`, `@Setter`, `@Data`
- `@Builder`
- `@Slf4j` for logging
## Testing
### Test Frameworks
- JUnit 4.12 for unit tests
- Mockito 5.0.0 for mocking
### Test Categories
**Unit Tests** (in each module's `src/test/java`)
- Standard JUnit tests
- Pattern: `*Test.java`
**Plugin E2E Tests** (`test/plugin/scenarios/`)
- 100+ test scenarios for plugin validation
- Docker-based testing with actual frameworks
- Pattern: `{framework}-{version}-scenario`
**End-to-End Tests** (`test/e2e/`)
- Full system integration testing
### Running Tests
```bash
# Unit tests
./mvnw test
# Full verification including checkstyle
./mvnw clean verify
# Skip tests during build
./mvnw package -Dmaven.test.skip=true
```
### Plugin Test Framework
The plugin test framework verifies plugin functionality using Docker containers with real services and a mock OAP backend.
#### Environment Requirements
- MacOS/Linux
- JDK 8+
- Docker & Docker Compose
#### Test Case Structure
**JVM-container (preferred):**
```
{scenario}-scenario/
├── bin/
│ └── startup.sh # JVM startup script (required)
├── config/
│ └── expectedData.yaml # Expected trace/meter/log data
├── src/main/java/... # Test application code
├── pom.xml
├── configuration.yml # Test case configuration
└── support-version.list # Supported versions (one per line)
```
**Tomcat-container:**
```
{scenario}-scenario/
├── config/
│ └── expectedData.yaml
├── src/main/
│ ├── java/...
│ └── webapp/WEB-INF/web.xml
├── pom.xml
├── configuration.yml
└── support-version.list
```
#### Key Configuration Files
**configuration.yml:**
```yaml
type: jvm # or tomcat
entryService: http://localhost:8080/case # Entry endpoint (GET)
healthCheck: http://localhost:8080/health # Health check endpoint (HEAD)
startScript: ./bin/startup.sh # JVM-container only
runningMode: default # default|with_optional|with_bootstrap
withPlugins: apm-spring-annotation-plugin-*.jar # For optional/bootstrap modes
environment:
- KEY=value
dependencies: # External services (docker-compose style)
mysql:
image: mysql:8.0
hostname: mysql
environment:
- MYSQL_ROOT_PASSWORD=root
```
**support-version.list:**
```
# One version per line, use # for comments
# Only include ONE version per minor version (not all patch versions)
4.3.6
4.4.1
4.5.0
```
**expectedData.yaml:**
Trace and meter expectations are typically in separate scenarios.
*For tracing plugins:*
```yaml
segmentItems:
- serviceName: your-scenario
segmentSize: ge 1 # Operators: eq, ge, gt, nq
segments:
- segmentId: not null
spans:
- operationName: /your/endpoint
parentSpanId: -1 # -1 for root span
spanId: 0
spanLayer: Http # Http, DB, RPC_FRAMEWORK, MQ, CACHE, Unknown
spanType: Entry # Entry, Exit, Local
startTime: nq 0
endTime: nq 0
componentId: 1
isError: false
peer: '' # Empty string for Entry/Local, required for Exit
skipAnalysis: false
tags:
- {key: url, value: not null}
- {key: http.method, value: GET}
- {key: http.status_code, value: '200'}
logs: []
refs: [] # SegmentRefs for cross-process/cross-thread
```
*For meter plugins:*
```yaml
meterItems:
- serviceName: your-scenario
meterSize: ge 1
meters:
- meterId:
name: test_counter
tags:
- {name: key1, value: value1} # Note: uses 'name' not 'key'
singleValue: gt 0 # For counter/gauge
- meterId:
name: test_histogram
tags:
- {name: key1, value: value1}
histogramBuckets: # For histogram
- 0.0
- 1.0
- 5.0
- 10.0
```
**startup.sh (JVM-container):**
```bash
#!/bin/bash
home="$(cd "$(dirname $0)"; pwd)"
# ${agent_opts} is REQUIRED - contains agent parameters
java -jar ${agent_opts} ${home}/../libs/your-scenario.jar &
```
#### Running Plugin Tests Locally
```bash
# Run a specific scenario
bash ./test/plugin/run.sh -f {scenario_name}
# IMPORTANT: Rebuild agent if apm-sniffer code changed
./mvnw clean package -DskipTests -pl apm-sniffer
# Use generator to create new test case
bash ./test/plugin/generator.sh
```
#### Adding Tests to CI
Add scenario to the appropriate `.github/workflows/` file:
- Use `python3 tools/select-group.py` to find the file with fewest cases
- **JDK 8 tests**: `plugins-test.<n>.yaml`
- **JDK 17 tests**: `plugins-jdk17-test.<n>.yaml`
- **JDK 21 tests**: `plugins-jdk21-test.<n>.yaml`
- **JDK 25 tests**: `plugins-jdk25-test.<n>.yaml`
```yaml
matrix:
case:
- your-scenario-scenario
```
#### Test Code Package Naming
- Test code: `org.apache.skywalking.apm.testcase.*`
- Code to be instrumented: `test.org.apache.skywalking.apm.testcase.*`
## Git Submodules
The project uses submodules for protocol definitions:
- `apm-protocol/apm-network/src/main/proto` - skywalking-data-collect-protocol
Always use `--recurse-submodules` when cloning or update submodules manually:
```bash
git submodule init && git submodule update
```
## IDE Setup (IntelliJ IDEA)
1. Import as Maven project
2. Run `./mvnw compile -Dmaven.test.skip=true` to generate protobuf sources
3. Mark generated source folders:
- `*/target/generated-sources/protobuf/java`
- `*/target/generated-sources/protobuf/grpc-java`
4. Enable annotation processing for Lombok
## Key Files for Understanding the Codebase
- `apm-sniffer/apm-agent/` - Agent entry point (premain)
- `apm-sniffer/apm-agent-core/src/main/java/.../enhance/` - Instrumentation engine
- `apm-sniffer/apm-agent-core/src/main/java/.../plugin/` - Plugin loading system
- `apm-sniffer/apm-sdk-plugin/` - All standard plugins (reference implementations)
- `apm-sniffer/config/agent.config` - Default agent configuration
## Common Development Tasks
### Adding a New Plugin
1. Create directory in `apm-sniffer/apm-sdk-plugin/{framework}-{version}-plugin/`
2. Implement instrumentation class using **V2 API** (e.g., extend `ClassInstanceMethodsEnhancePluginDefineV2`)
3. Implement interceptor class using **V2 API** (e.g., implement `InstanceMethodsAroundInterceptorV2`)
4. Register plugin in `skywalking-plugin.def` file
5. Add test scenario in `test/plugin/scenarios/`
### Adding an Optional Plugin
1. Create in `apm-sniffer/optional-plugins/`
2. Update documentation in `docs/en/setup/service-agent/java-agent/Optional-plugins.md`
### Modifying Agent Configuration
1. Edit `apm-sniffer/config/agent.config`
2. Update documentation if adding new options
## Documentation
- `docs/en/setup/service-agent/java-agent/` - Main agent documentation
- `docs/en/setup/service-agent/java-agent/Plugin-list.md` - Complete plugin list
- `docs/en/setup/service-agent/java-agent/Optional-plugins.md` - Optional plugins guide
- `CHANGES.md` - Changelog (update when making changes)
## Community
- GitHub Issues: https://github.com/apache/skywalking-java/issues
- Mailing List: dev@skywalking.apache.org
- Slack: #skywalking channel at Apache Slack
## Submitting Pull Requests
### Branch Strategy
- **Never work directly on main branch**
- Create a new branch for your changes
### PR Template
Follow `.github/PULL_REQUEST_TEMPLATE` based on change type:
- **Bug fix**: Add unit test, explain bug cause and fix
- **New plugin**: Add test case, component ID in OAP, logo in UI repo
- **Performance improvement**: Add benchmark with results, link to theory/discussion
- **New feature**: Link design doc if non-trivial, update docs, add tests
### PR Requirements
- Follow Apache Code of Conduct
- Include updated documentation for new features
- Include tests for new functionality
- Reference original issue (e.g., "Resolves #123")
- Update `CHANGES.md` for user-facing changes
- Pass all CI checks (checkstyle, tests, license headers)
### PR Description
- Bug fixes: Explain the bug and how it's fixed, add regression test
- New features: Link to design doc if non-trivial, update docs, add tests
- Do NOT add AI assistant as co-author
## CI/CD
GitHub Actions workflows:
- **CI**: Multi-OS (Ubuntu, macOS, Windows), Multi-Java (8, 11, 17, 21, 25)
- **Plugin Tests**: Parallel E2E tests for all plugins
- **E2E Tests**: Full system integration
- **Docker Publishing**: Multi-variant images
## Tips for AI Assistants
1. **Use V2 instrumentation API**: Always use V2 classes (`ClassEnhancePluginDefineV2`, `InstanceMethodsAroundInterceptorV2`) for new plugins; V1 is legacy
2. **NEVER use `.class` references**: In instrumentation definitions, always use string literals for class names (e.g., `byName("com.example.MyClass")` not `byName(MyClass.class.getName())`)
3. **Always set component and layer**: For EntrySpan and ExitSpan, always call `setComponent()` and `setLayer()`
4. **Prefer `byName` for class matching**: Avoid `byHierarchyMatch` unless necessary (causes performance issues)
5. **Use witness classes for version-specific plugins**: Implement `witnessClasses()` or `witnessMethods()` to activate plugins only for specific library versions
6. **Always check submodules**: Protocol changes may require submodule updates
7. **Generate sources first**: Run `mvnw compile` before analyzing generated code
8. **Respect checkstyle**: No System.out, no @author, no Chinese characters
9. **Follow plugin patterns**: Use existing V2 plugins as templates
10. **Use Lombok**: Prefer annotations over boilerplate code
11. **Test both unit and E2E**: Different test patterns for different scopes
12. **Plugin naming**: Follow `{framework}-{version}-plugin` convention
13. **Shaded dependencies**: Core dependencies are shaded to avoid classpath conflicts
14. **Java version compatibility**: Agent core must maintain Java 8 compatibility, but individual plugins may target higher JDK versions (e.g., jdk-httpclient-plugin for JDK 11+, virtual-thread plugins for JDK 21+)
15. **Bootstrap instrumentation**: Only use for JDK core classes, and only when absolutely necessary
16. **Register plugins**: Always add plugin definition to `skywalking-plugin.def` file