This repository provides:
- a Gradle plugin,
de.burger.forensics.btmgen, that scans Java source code - Maven plugin goals, including
forensics:btmgenandforensics:analyze, backed by the same generation core - generation of Byteman
.btmrules - runtime tracing helpers centered around
de.burger.forensics.infrastructure.rt.RtTrace - an application-facing tracing facade,
de.burger.forensics.application.tracing.Tracer - optional AspectJ-based method logging via
de.burger.forensics.infrastructure.logging.MethodLoggingAspect
Internally the repository is split into pragmatic hexagonal layers (domain, application, adapters, plugin, infrastructure). The Gradle tasks and Maven Mojos sit in build-tool adapter packages and delegate to shared build-tool-neutral runners.
Prerequisites:
- Java 17
- Gradle 9.4.0
- a consumer Java project
- Byteman agent/tooling if you want the generated rules to run inside a JVM
Add the plugin through a composite build:
// settings.gradle.kts
pluginManagement {
includeBuild("../forensics_tracing")
repositories {
gradlePluginPortal()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
}
}
rootProject.name = "consumer-project"Apply the plugin and configure the default output:
// build.gradle.kts
plugins {
java
id("de.burger.forensics.btmgen")
}
btmGen {
sourceRoot.set(file("src/main/java"))
outputFile.set(layout.buildDirectory.file("forensics/forensics.btm").get().asFile)
}Generate rules and inspect the result:
./gradlew generateBtmRules
cat build/forensics/forensics.btmThe Gradle task also writes the static analysis package by default:
build/forensics/forensics.btm
build/forensics/manifest.json
build/forensics/checksums.sha256
build/forensics/analysis-store/
Then:
- Keep the generated file at
build/forensics/forensics.btm. - Enable runtime tracing with
-Dforensics.rt.enabled=true. - Optionally add
-Dforensics.rt.output=logs/trace.jsonfor file output. - Load the generated
.btmfile with your normal Byteman agent or tooling setup.
Warning:
generateBtmRulesonly generates build artifacts. It does not instrument a JVM.Warning: Byteman loading and runtime tracing are separate steps.
Prerequisites:
- Java 17
- Maven 3.9.x or a compatible Maven runtime
- the plugin artifact installed or published to a Maven repository
- Byteman agent/tooling if you want the generated rules to run inside a JVM
The current project coordinates are:
de.burger.forensics:forensics-tracing:0.0.3-SNAPSHOT
For local testing, publish the Gradle-built artifact to your local Maven repository first:
./gradlew publishToMavenLocalThen invoke the Mojo with the full coordinate:
mvn de.burger.forensics:forensics-tracing:0.0.3-SNAPSHOT:btmgenThe Maven goal writes the same static analysis package shape as the Gradle task by default:
target/forensics/generated.btm
target/forensics/manifest.json
target/forensics/checksums.sha256
target/forensics/analysis-store/
For a Maven reactor, run the aggregate goal from the root project:
mvn de.burger.forensics:forensics-tracing:0.0.3-SNAPSHOT:btmgen-aggregateThe aggregate goal uses reactor module source roots. A root project with pom packaging is a valid aggregation context and does not need its own src/main/java.
For full BTM generation plus Joern semantic enrichment, enable Joern and use the full-analysis goals:
mvn de.burger.forensics:forensics-tracing:0.0.3-SNAPSHOT:analyze -Dforensics.joernEnabled=true
mvn de.burger.forensics:forensics-tracing:0.0.3-SNAPSHOT:analyze-aggregate -Dforensics.joernEnabled=trueMinimal pom.xml configuration:
<plugin>
<groupId>de.burger.forensics</groupId>
<artifactId>forensics-tracing</artifactId>
<version>0.0.3-SNAPSHOT</version>
<configuration>
<cacheEnabled>false</cacheEnabled>
<strictParsing>false</strictParsing>
<strictConditionValidation>false</strictConditionValidation>
<outputFile>${project.build.directory}/forensics/generated.btm</outputFile>
</configuration>
</plugin>The short mvn forensics:btmgen form requires Maven plugin prefix resolution for the de.burger.forensics group. Use the full coordinate when that prefix metadata is not available in the configured repositories.
Use a composite build so the consumer project resolves this plugin directly from the repository:
pluginManagement {
includeBuild("../forensics_tracing")
repositories {
gradlePluginPortal()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
}
}
rootProject.name = "consumer-project"Run the consumer build on Java 17 and Gradle 9.4.0. The repository intentionally does not target Java 21.
Use the verified plugin ID:
plugins {
java
id("de.burger.forensics.btmgen")
}Notes:
btmGenis the verified extension name.generateBtmRulesis the verified task name.- When a Java plugin is present, the plugin automatically attaches its runtime helper artifact to
runtimeOnlyandtestRuntimeOnly.
Start with the minimal scan-mode setup:
btmGen {
sourceRoot.set(file("src/main/java"))
outputFile.set(layout.buildDirectory.file("forensics/forensics.btm").get().asFile)
}This is enough for a normal src/main/java project.
Run:
./gradlew generateBtmRulesThe plugin scans Java sources, writes one Byteman script, and prepares the static analysis store artifacts.
Warning:
generateBtmRuleswrites build artifacts only. It does not attach Byteman and does not start tracing on its own.
For Gradle builds, generateBtmRules creates a persistent analysis package under build/forensics by default:
forensics.btm: Byteman rules with a comment header containing the analysis identitymanifest.json: machine-readable metadata for the generated packagechecksums.sha256: SHA-256 checksums for the BTM file, manifest, and H2 filesanalysis-store/: H2 database files containing analysis runs, source files, scan events, methods, BTM rules, and artifact checksums
The store is controlled through:
btmGen {
analysisStoreEnabled.set(true)
analysisStoreDirectory.set(file("build/forensics/analysis-store"))
cleanupPolicy.set("KEEP_ON_SUCCESS")
projectKey.set("legacy-demo-shop")
manifestFile.set(file("build/forensics/manifest.json"))
checksumsFile.set(file("build/forensics/checksums.sha256"))
engineRequestEnabled.set(true)
engineRequestFile.set(file("build/forensics/engine-request.json"))
}Supported cleanup policies are DELETE_ON_SUCCESS, KEEP_ON_SUCCESS, KEEP_ON_FAILURE, and KEEP_ALWAYS.
Set analysisStoreEnabled=false to keep the previous Gradle behavior of writing only the .btm file.
Run ./gradlew cleanForensicsAnalysisStore to delete the generated analysis store, manifest, checksum file, and Joern work directories without wiring that cleanup into the normal clean lifecycle.
For Maven builds, run mvn de.burger.forensics:forensics-tracing:0.0.3-SNAPSHOT:clean-analysis for the equivalent generated-artifact cleanup.
When engineRequestEnabled=true, Gradle and Maven also write an engine-request.json artifact that describes the generated analysis payloads for a later forensic_analytics engine upload. The legacy local BTM and analysis-store outputs remain the default behavior. Direct gRPC publishing, server upload, replay, and LLM context generation are not implemented by this feature.
Joern enrichment is optional and disabled by default. It is not part of the normal build lifecycle.
Run the explicit Gradle aggregate task when a generated analysis package should be enriched with Joern artifacts and H2 semantic tables:
./gradlew forensicsAnalyzeThe aggregate task runs generateBtmRules, analyzeForensicsSemantics, and importForensicsSemantics.
analyzeForensicsSemantics fails with a clear message unless btmGen.joernEnabled=true.
Joern is invoked as an external CLI; it is not added as a Java dependency.
btmGen {
joernEnabled.set(true)
joernExecutable.set(file("/opt/joern/joern"))
joernParseExecutable.set(file("/opt/joern/joern-parse"))
joernSliceExecutable.set(file("/opt/joern/joern-slice"))
joernWorkspaceDirectory.set(file("build/forensics/joern/workspace"))
joernOutputDirectory.set(file("build/forensics/joern"))
joernTimeoutSeconds.set(300)
joernFailOnError.set(true)
}When enabled, the package additionally contains build/forensics/joern/cpg.bin, callgraph.json, controlflow.json, dataflow.json, and slices.json.
The H2 store receives joern_import_run, graph, relation, data-flow, and semantic_anchor rows, and the manifest/checksum files include the Joern artifacts.
Maven exposes the same semantic enrichment flow through module-local and reactor goals:
mvn de.burger.forensics:forensics-tracing:0.0.3-SNAPSHOT:analyze-semantics -Dforensics.joernEnabled=true
mvn de.burger.forensics:forensics-tracing:0.0.3-SNAPSHOT:import-semantics -Dforensics.joernEnabled=true
mvn de.burger.forensics:forensics-tracing:0.0.3-SNAPSHOT:analyze -Dforensics.joernEnabled=true
mvn de.burger.forensics:forensics-tracing:0.0.3-SNAPSHOT:analyze-aggregate -Dforensics.joernEnabled=trueMaven Joern parameters use the same names with the forensics.* user-property prefix, for example -Dforensics.joernExecutable=/opt/joern/joern.
The default Maven executable values are command names (joern, joern-parse, joern-slice) and are resolved through PATH; configured relative paths such as tools/joern are resolved against the Maven project base directory.
The Maven defaults write Joern artifacts under target/forensics/joern.
Inspect the output:
cat build/forensics/forensics.btmThe default output location is build/forensics/forensics.btm. By default the file starts with # Generated Byteman rules, includes analysis identity comments, omits a timestamp, and then contains rendered RULE blocks.
Enable runtime tracing separately:
-Dforensics.rt.enabled=trueOptional file output:
-Dforensics.rt.output=logs/trace.jsonWarning: Runtime tracing is opt-in. Generated rules can call the helper, but no runtime events are emitted until tracing is enabled.
This repository does not provide one canonical startup command for every target JVM. Load the generated file through your normal Byteman mechanism, such as your existing agent startup flags, container startup settings, or test harness integration.
Warning: Byteman must be attached or loaded separately. Rule generation alone is not instrumentation.
Use the plugin on a root project when you want one aggregated .btm file for several Java subprojects:
plugins {
id("de.burger.forensics.btmgen")
}
btmGen {
scanSubprojects.set(true)
outputFile.set(layout.buildDirectory.file("forensics/all-modules.btm").get().asFile)
}Verified monorepo behavior:
- the root project does not need its own
src/main/java - Java subprojects are discovered through their
mainSourceSet - non-Java subprojects are ignored
- empty Java subprojects do not fail the task
- custom
sourceSets.main.java.srcDirs(...)directories are supported - duplicate roots are de-duplicated before scanning
- explicit
sourceRootsare combined withscanSubprojects=true - included builds and composite builds are not auto-scanned
Add external or included-build roots explicitly:
btmGen {
scanSubprojects.set(true)
sourceRoots.from(
file("../included-build/some-module/src/main/java"),
file("legacy-module/src/main/java")
)
}Warning:
scanSubprojects=truescans Gradle subprojects of the current build only. It does not auto-scan included builds or composite builds.
Prefer the Maven Mojo for Maven projects:
mvn de.burger.forensics:forensics-tracing:0.0.3-SNAPSHOT:btmgenThe module-local Maven goal runs for the current Maven project/module and uses that module's compile source roots.
Use the aggregate goal from a Maven reactor root when one generated package should include all reactor modules:
mvn de.burger.forensics:forensics-tracing:0.0.3-SNAPSHOT:btmgen-aggregateVerified Maven reactor behavior:
- the root project may use
pompackaging and have no source roots - reactor modules with compile source roots are scanned
- modules without existing source roots are ignored
includeTests=trueadds Maven test compile source roots- duplicate roots are de-duplicated before scanning
For Gradle sidecar builds that scan an external Maven-style source tree, keep scanSubprojects=false and pass discovered src/main/java directories explicitly through btmGen.sourceRoots. scanSubprojects=true only applies to Gradle subprojects in the current build.
Linux, macOS, or Git Bash:
./gradlew tasks --group verification
./gradlew generateBtmRules
test -f build/forensics/forensics.btm
grep "RULE" build/forensics/forensics.btmWindows PowerShell:
.\gradlew.bat generateBtmRules
Test-Path .\build\forensics\forensics.btm
Select-String -Path .\build\forensics\forensics.btm -Pattern "RULE"Release-relevant behavior for this repository:
- Gradle 9.4.0 is the baseline.
- Java 17 is the baseline.
- Default generated
.btmoutput is deterministic. includeTimestampHeader=truedisables Gradle task output caching intentionally.analysisStoreEnabled=truewrites local H2 analysis state and disables Gradle task output caching for that task execution.- the built-in strategy registry is configuration-cache safe
- custom
StrategyRegistryinstances are supported for normal builds, but they are not guaranteed to restore safely when Gradle reuses the configuration cache - monorepo support covers Gradle subprojects in the current build
- composite-build or included-build source trees must be supplied explicitly through
sourceRoots
Warning: Timestamp output is intentionally non-cacheable because it makes the generated file time-dependent.
The verified configuration split is:
- Extension
btmGensourceRootsourceRootsscanSubprojectsincludeTestsincludeTimestampHeaderoutputFilehelperFqnincludesexcludesminBranchesPerMethodstrictConditionValidationanalysisStoreEnabledanalysisStoreDirectorycleanupPolicyprojectKeymanifestFilechecksumsFile
- Task
generateBtmRulesincludeEntryExitminBranchesPerMethodstrictConditionValidation- single-template inputs such as
templateId,className,methodName,methodDesc
minBranchesPerMethod exists on both the extension and the task. The plugin copies the extension value into the task as a convention, and the task can override it explicitly.
Verified Kotlin DSL example:
btmGen {
sourceRoot.set(file("src/main/java"))
sourceRoots.from(file("shared-generated/java"))
outputFile.set(file("build/forensics/forensics.btm"))
analysisStoreEnabled.set(true)
analysisStoreDirectory.set(file("build/forensics/analysis-store"))
cleanupPolicy.set("KEEP_ON_SUCCESS")
projectKey.set("consumer-project")
manifestFile.set(file("build/forensics/manifest.json"))
checksumsFile.set(file("build/forensics/checksums.sha256"))
helperFqn.set("de.burger.forensics.infrastructure.rt.RtTraceHelper")
includes.set("com.example,org.acme")
excludes.set("com.example.generated")
includeTests.set(false)
minBranchesPerMethod.set(2)
strictConditionValidation.set(false)
}
tasks.named<de.burger.forensics.plugin.btmgen.gradle.GenerateBtmTask>("generateBtmRules") {
includeEntryExit.set(true)
minBranchesPerMethod.set(2)
}The Maven goals are:
forensics:btmgen
forensics:btmgen-aggregate
forensics:analyze-semantics
forensics:import-semantics
forensics:analyze
forensics:analyze-aggregate
forensics:clean-analysis
Supported Maven parameters use the forensics.* user-property prefix:
sourceRootoutputFilecacheEnabledcacheBackendcacheDatabaseFileprofilingEnabledprofileReportFilestrictParsingstrictConditionValidationdependencyAwareInvalidationincludePackagesexcludePackagesincludeTestshelperFqnincludeEntryExitminBranchesPerMethodincludeTimestampHeaderanalysisStoreEnabledanalysisStoreDirectorycleanupPolicyprojectKeypluginVersionmanifestFilechecksumsFileengineRequestEnabledengineRequestFilejoernEnabledjoernExecutablejoernParseExecutablejoernSliceExecutablejoernWorkspaceDirectoryjoernOutputDirectoryjoernMaxHeapjoernTimeoutSecondsjoernFailOnError
Maven connector behavior:
btmgen,analyze-semantics,import-semantics, andanalyzerun for the current Maven project/module- it uses Maven compile source roots by default
includeTests=trueadds Maven test compile source roots- a configured
sourceRootoverrides Maven project roots btmgen-aggregateuses Maven reactor projects fromMavenSessionanalyze-aggregateuses Maven reactor projects fromMavenSession- a reactor root with
pompackaging does not need its own source roots - empty reactor modules are ignored safely
analysisStoreEnabled=truewritesmanifest.json,checksums.sha256, and the H2 analysis store by defaultanalysisStoreEnabled=falsewrites only the.btmfile plus local scanner cache state when caching is enabled
The Maven adapter must stay thin. It must not call GenerateBtmTask and must not duplicate JavaParser scanning or Byteman rendering logic.
Property behavior:
sourceRoot- default:
src/main/java - legacy single-root alias kept for backward compatibility
- missing directories are ignored instead of failing the task
- default:
sourceRoots- additional explicit scan roots
- combined with
sourceRootand auto-discovered GradleSourceSetroots - duplicate roots are de-duplicated before scanning
- use this for legacy folders, external folders, and included/composite build sources
scanSubprojects- default:
false - scans the
mainSourceSetof Java subprojects in the current Gradle build - non-Java subprojects are ignored
- empty Java subprojects are ignored safely
- the root project does not need its own
src/main/java - included or composite builds are not auto-discovered through this flag
- default:
includeTests- default:
false - when
true, Gradle also scans thetestSourceSetroots for the current project and scanned subprojects - Maven maps the same behavior to Maven test compile source roots
- default:
includeTimestampHeader- default:
false - when
false, the generated header stays deterministic and the task can participate in the build cache - when
true, the writer adds# Timestamp: ...andGenerateBtmTaskexplicitly opts out of task output caching
- default:
analysisStoreEnabled- default for Gradle and Maven:
true - when
true, the task writesmanifest.json,checksums.sha256, and an H2 analysis store next to the.btmfile - when
false, connector output stays limited to the.btmfile and local scanner cache state when caching is enabled
- default for Gradle and Maven:
engineRequestEnabled- default for Gradle and Maven:
false - when
true, connector output includes anengine-request.jsonhandoff artifact forforensic_analytics - the request describes stable payload IDs, payload kinds, content types, and local artifact files; it does not perform a network upload
- default for Gradle and Maven:
engineRequestFile- Gradle default:
build/forensics/engine-request.json - Maven default:
target/forensics/engine-request.json
- Gradle default:
cleanupPolicy- default:
KEEP_ON_SUCCESS - controls whether the H2 analysis store directory is retained after task execution
- default:
joernEnabled- default:
false - enables explicit Joern semantic enrichment tasks; Joern still does not run during normal
build
- default:
joernExecutable,joernParseExecutable,joernSliceExecutable- defaults:
joern,joern-parse,joern-slice - external CLI executables used by
analyzeForensicsSemanticsand the Maven semantic/full-analysis goals - Maven keeps simple executable names as
PATHcommands; Maven relative paths containing/or\are resolved against the project base directory
- defaults:
joernWorkspaceDirectory- Gradle default:
build/forensics/joern/workspace - Maven default:
target/forensics/joern/workspace
- Gradle default:
joernOutputDirectory- Gradle default:
build/forensics/joern - Maven default:
target/forensics/joern
- Gradle default:
joernTimeoutSeconds- default:
300
- default:
joernFailOnError- default:
true - when
false, failed Joern commands are tolerated and available artifacts are parsed
- default:
outputFile- default:
build/forensics/forensics.btm - the task writes exactly one
.btmfile there by default
- default:
helperFqn- default:
de.burger.forensics.infrastructure.rt.RtTraceHelper - blank values normalize back to that default
- default:
includes- extension-only
- comma-separated fully qualified package or class prefixes
- example:
com.example,org.acme
excludes- extension-only
- comma-separated fully qualified package or class prefixes to exclude
- example:
com.example.generated,org.acme.legacy
includeEntryExit- task-only
- default:
true - when
false, scan mode does not addMETHOD_ENTERandMETHOD_EXITrules
minBranchesPerMethod- default:
2 - methods with fewer than that many branch events (
IF_TRUE,IF_FALSE,SWITCH,SWITCH_CASE) are filtered out from the final output
- default:
Run:
./gradlew generateBtmRulesVerified behavior:
- the default output file is
build/forensics/forensics.btm - Gradle also writes
build/forensics/manifest.json,build/forensics/checksums.sha256, andbuild/forensics/analysis-store/by default - the default file content is deterministic
- the file contains
# Generated Byteman rules, analysis identity comments, no timestamp line by default, and the rendered rule blocks - scan mode is the default mode
- the plugin also wires
generateBtmRulesintobuildwhen abuildtask exists includeTimestampHeader=truekeeps the task functional but disables task output caching
Warning: The generated
.btmfile is only a Byteman script artifact. It is not active until you load it into a JVM with Byteman tooling.
The generated output is a Byteman script. To execute the rules:
- load the generated
.btmfile into a JVM with your normal Byteman mechanism - make sure the helper class referenced by the rules is on the runtime classpath
- enable runtime tracing separately
The default helper is de.burger.forensics.infrastructure.rt.RtTraceHelper. That helper forwards events to RtTrace.
Enable runtime tracing with either:
-Dforensics.rt.enabled=trueFORENSICS_RT_ENABLED=true
Optional file output:
-Dforensics.rt.output=logs/trace.json
Verified runtime output behavior:
RtTraceemits one JSON line per event to a dedicated JUL console logger- with the standard JDK
ConsoleHandler, that is console output and is typically written to stderr rather than stdout - when
forensics.rt.outputis set, the same JSON lines are also appended to the configured file - the payload includes timestamp, event name, thread, optional correlation ID, optional span ID, a
detailsobject, and optional error fields
This repository does not provide one canonical JVM startup command for Byteman. Use your normal Byteman loading mechanism for the target application or test process.
Use de.burger.forensics.application.tracing.Tracer when application code should stay independent from direct RtTrace calls.
The current interface exposes:
enter(...)exit(...)span(...)branch(...)setVariable(...)error(...)setCorrelationId(...)newCorrelationId()
The repository already contains a concrete implementation:
de.burger.forensics.infrastructure.rt.RtTracer
Typical usage:
Tracer tracer = new de.burger.forensics.infrastructure.rt.RtTracer();
String correlationId = tracer.newCorrelationId();
tracer.setCorrelationId(correlationId);
try (AutoCloseable span = tracer.span("checkout")) {
tracer.enter(OrderService.class, "placeOrder", orderId);
tracer.branch("discountApplied", true);
tracer.setVariable("orderTotal", total);
tracer.exit(OrderService.class, "placeOrder", result);
} catch (Exception ex) {
tracer.error(ex);
throw ex;
}The repository also contains examples/RtTracerAdapter.java, which shows the same adapter pattern. However, that example still uses the older var(...) method name. The current Tracer interface uses setVariable(...), so align the example with the interface before copying it into production code.
The renderer supports these RuleTemplate values:
| Rule type | Produced from source scanning | Produced in single-template mode | Runtime callback |
|---|---|---|---|
METHOD_ENTER |
Yes. Also synthesized per method when includeEntryExit=true and no explicit enter event exists. |
Yes | onEnter(...) |
METHOD_EXIT |
Yes. Also synthesized per method when includeEntryExit=true and no explicit exit event exists. |
Yes | onExit(..., null) |
RETURN |
Yes | Yes | onExit(..., $!) |
THROW |
Yes | Yes | onException($^) |
IF_TRUE |
Yes | Yes | onBranch(..., "IF_TRUE") with eval(...) guard logic |
IF_FALSE |
Yes | Yes | onBranch(..., "IF_FALSE") with eval(...) guard logic |
SWITCH |
Yes | Yes | onSwitch(...) |
SWITCH_CASE |
Yes | Yes | onCase(...) |
THREAD_LIFECYCLE |
No. GenerateRulesUseCase explicitly skips it in scan mode. |
Yes | threadFork(...) / threadJoin(...) |
JDBC_EXECUTE |
No. GenerateRulesUseCase explicitly skips it in scan mode. |
Yes | ioBegin(...) / ioEnd(...) |
Notes:
THREAD_LIFECYCLErenders fixed rules forjava.lang.Thread.start()andjava.lang.Thread.join(..).JDBC_EXECUTErenders fixed rules forjava.sql.Statementexecute-style methods.- The scanner-backed path currently generates
METHOD_ENTER,METHOD_EXIT,RETURN,THROW,IF_TRUE,IF_FALSE,SWITCH, andSWITCH_CASE.
The verified build-tool adapter split is:
GenerateBtmTask ─┐
├── BtmGenerationRunner ─── Scanner / UseCase / Renderer / Writer
BtmGenMojo ────┘
The shared runner owns scanner, rule-generation, rendering, profiling, cache selection, and file writing orchestration. Build-tool adapters only map Gradle or Maven parameters into BtmGenerationRequest, invoke the runner, and translate logging or exceptions.
The verified scan execution flow is:
- The Gradle task
generateBtmRules(GenerateBtmTask) or Maven goalforensics:btmgenstarts in scan mode by default. - The build-tool adapter resolves one or more Java source roots and creates a build-tool-neutral
BtmGenerationRequest. JavaParserScannerscans those roots with JavaParser-based support classes and producesScanEventinstances.GenerateRulesUseCasefilters scan events by language and optional package prefixes, groups them by method, optionally adds syntheticMETHOD_ENTERandMETHOD_EXITrules, and applies theminBranchesPerMethodfilter.- The application layer turns scan events into domain
Ruleobjects typed byRuleTemplate. BytemanRuleRenderAdapterconverts each domain rule intoRuleParams, andBytemanRuleRendererdispatches to the matching render strategy.BtmFileWriterwrites one.btmfile containing a deterministic generated header plus all rendered rule blocks, unless an optional timestamp header is explicitly enabled.- That
.btmfile is not active by itself. You must load it into a JVM with Byteman tooling. - At runtime, generated rules call helper methods on the configured helper class. The default helper is
RtTraceHelper, which delegates toRtTrace. RtTraceemits structured runtime events and manages correlation IDs and span state.
GenerateBtmTask also has a manual single-template mode. In that mode it skips source scanning entirely and renders one explicit template from task inputs.
Architecture rules under src/test/java/de/burger/forensics/quality enforce that:
plugin.btmgen.commondoes not depend on Gradle or Maven APIsplugin.btmgen.gradledoes not depend on Maven APIsplugin.btmgen.mavendoes not depend on Gradle APIsdomain,application, and JavaParser scanner packages do not depend on build-tool APIs
GenerateBtmTask switches from scan mode to manual single-template mode only when all of these task inputs are present:
templateIdclassNamemethodName
methodDesc is optional.
Minimal example:
tasks.named<de.burger.forensics.plugin.btmgen.gradle.GenerateBtmTask>("generateBtmRules") {
templateId.set("METHOD_ENTER")
className.set("com.example.OrderService")
methodName.set("placeOrder")
methodDesc.set("(Ljava/lang/String;)V")
}Notes:
- this mode bypasses Java source scanning
- if
templateIdis blank but still present, the task normalizes it toMETHOD_ENTER THREAD_LIFECYCLEandJDBC_EXECUTEare primarily useful in this mode because scan mode skips them- the checked-in renderers for
THREAD_LIFECYCLEandJDBC_EXECUTEemit fixed targets (java.lang.Threadandjava.sql.Statementexecute methods), even though the task still requiresclassNameandmethodNameto enter single-template mode
MethodLoggingAspect is separate from Byteman rule generation and separate from runtime tracing.
Verified behavior:
- it logs method entry, successful return, and thrown exceptions
- it targets public methods matching:
de.burger.forensics..*org.example.trace..*
- it skips methods annotated with
de.burger.forensics.infrastructure.logging.SuppressLogging - it writes through the target class logger and also mirrors log lines to a file
Verified file-logging properties:
forensics.btmgen.logToFile- default in the aspect implementation:
true
- default in the aspect implementation:
forensics.btmgen.logFile- default in the aspect implementation:
logs/forensics-btmgen.log
- default in the aspect implementation:
Verified weaving setup in this repository:
src/main/resources/META-INF/aop.xmlincludesde.burger.forensics..*- the same
aop.xmlexcludesde.burger.forensics.infrastructure.logging..* - repository tests run with the AspectJ weaver as a Java agent
About forensics.aspect.enabled:
- the repository build sets
forensics.aspect.enabled=truein test configuration - the checked-in
MethodLoggingAspectclass does not read that property directly - treat that flag as part of the surrounding build or weaving setup, not as a property consumed by the aspect implementation itself
-
Wrong task name: Use
generateBtmRules. Old names from earlier documentation are stale. -
Wrong extension name: Use
btmGen, not a lowercase or differently spelled variant. -
No useful output because of the wrong source root:
GenerateBtmTaskfilters source roots that do not exist. If you configured the wrongsourceRootorsourceRoots, the task can still create the output file but it may contain only the generated header and no real rules. -
scanSubprojectsmisses code from another build:scanSubprojectsonly scans Gradle subprojects of the current build. For included builds, composite builds, or arbitrary external folders, add those directories explicitly withsourceRoots. -
includeTimestampHeader=truedoes not reuse the build cache: That is expected. Timestamped output is intentionally non-deterministic, soGenerateBtmTaskdisables task output caching for that mode. -
Configuration cache fails after configuring a custom
StrategyRegistry: The built-in registry is configuration-cache safe. Custom registries are supported for normal builds, but they are not guaranteed to restore when the configuration cache is reused. Disable configuration cache for that build or use the built-in registry. -
No runtime trace appears: Generating
.btmfiles is not enough. You must both load the script with Byteman and enable tracing with-Dforensics.rt.enabled=trueorFORENSICS_RT_ENABLED=true. -
Confusing rule generation with instrumentation:
./gradlew generateBtmRulesonly writes build artifacts. It does not attach Byteman, instrument a JVM, or start runtime tracing by itself. -
Confusing plugin-generated rules with AspectJ logging:
MethodLoggingAspectis separate from Byteman. You can use one without the other. -
Package filtering matches nothing:
includesis a comma-separated prefix filter over fully qualified class names. If no class name starts with one of those prefixes, scan mode produces no matching rules. -
Condition validation warnings: Generation reports suspicious simple type references such as
DeploymentType.EARbecause Byteman may not resolve source imports while loading a.btmfile. The default is warning-only reporting. SetstrictConditionValidation=trueto fail generation on those findings. Known limitations: the validator cannot reconstruct every source import, debug local-variable table, or runtime classloader lookup, and it treats$name/$1Byteman variables as already resolved. -
Blank helper FQCN does not stay blank: Both
GenerationRequestandRuleParamsnormalize blank helper values back tode.burger.forensics.infrastructure.rt.RtTraceHelper. -
THREAD_LIFECYCLEorJDBC_EXECUTEdo not appear during scanning: That is expected. The scan path skips those templates. Use single-template mode for them. -
includeEntryExit=falsestill leaves some rules: That flag only suppresses generatedMETHOD_ENTERandMETHOD_EXITrules. It does not suppressIF_*,SWITCH*,RETURN, orTHROW.
Use ./gradlew on Unix-like shells or .\gradlew.bat on Windows PowerShell.
Verified repository commands:
./gradlew build
./gradlew test
./gradlew clean test jacocoTestReport jacocoTestCoverageVerification checkPackageCoverage
./gradlew publishToMavenLocalNotes:
- Java 17 is intentional for this repository.
- Gradle 9.4.0 is the repository baseline.
- Source code, source comments, test names, and repository documentation are maintained in English.
- the stricter local quality gate from
QUALITY.mdis./gradlew clean test jacocoTestReport jacocoTestCoverageVerification checkPackageCoverage - the repository does not assume or require a Java 21 migration
The test suite in src/test/java verifies the behavior described above. In particular:
-
BtmGenPluginTest- plugin ID application
- extension and task registration
- default conventions
- runtime helper attachment to
runtimeClasspathandtestRuntimeClasspath - monorepo runtime helper attachment to Java subprojects
buildwiring
-
BtmGenPluginFunctionalTest- root-project monorepo scanning without a root
src/main/java - custom
mainsource-set directories in subprojects UP-TO-DATEbehavior and reruns when sources change- build-cache and configuration-cache coverage
- root-project monorepo scanning without a root
-
GenerateBtmTaskTest- scan mode
- single-template mode
- multi-root and subproject scanning
- missing explicit roots and missing legacy
sourceRoot - subproject
SourceSetdiscovery and customsourceSets.main.java.srcDirs(...) - helper FQCN normalization
- duplicate root deduplication and duplicate
RULEheader deduplication THREAD_LIFECYCLEandJDBC_EXECUTEmanual rendering paths
-
BtmGenMojoTest- Maven parameter mapping into the shared runner
- explicit
sourceRoothandling - clear failure behavior for missing roots and unsupported cache backends
-
MavenAnalysisStoreParityTest- Maven Analysis Store output
- manifest/checksum generation
analysisStoreEnabled=falsebehavior
-
MavenReactorAggregationTest- Maven root
pomaggregation - empty reactor module handling
includeTests=truereactor behavior
- Maven root
-
MavenJoernConfigurationParityTest- Maven Joern parameter mapping into the shared semantic request
- Maven semantic import verification
-
MavenFullAnalysisParityTest- Maven module and reactor full-analysis goals
- fake Joern semantic import without requiring a local Joern installation
-
CleanForensicsAnalysisMojoTest- Maven generated analysis artifact cleanup
-
BtmGenerationAdapterValidationTest- deterministic direct runner output
- byte-identical output from the direct runner, Gradle task, and Maven Mojo for the same fixture
-
IfRuleStrategyTestIF_TRUEandIF_FALSErendering at source lineseval(...)expression generation- placeholder stripping and static field qualification
-
GenerationRequestTest- blank helper normalization
- null handling
- immutable copies of optional collections
-
QUALITY.md- explains the repository quality gate
- documents
checkPackageCoverage - points to the SOLID-oriented and architecture-oriented test suites