Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
- Provider-neutral eval corpus and repository validation gate.
- `btrace-legacy-libs-migration` skill for moving retired `libs`/profile packaging into an extension.
- Moved `btrace-extensions-and-permissions` into `btrace-development`; extension authoring is build-time work, not incident diagnosis.
- `btrace-extension-authoring` skill for designing a new extension against a target library.
18 changes: 18 additions & 0 deletions evals/btrace-observability.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,5 +167,23 @@
"grantAll=true"
],
"rubric": "Recognise that libs= no longer loads anything and the missing classes are the symptom. Direct to one extension project with a single source set and declared services, not separate API and impl modules. Keep permission grants in permissions.properties rather than extensions.conf. Include verification by attaching and taking a trace."
},
{
"id": "extension-authoring-target-library",
"prompt": "I want to trace a third-party job scheduler library from BTrace. Its listener class is only on the application classpath and some methods only exist in newer versions. How should I structure the extension?",
"requiredSkills": [
"btrace-extension-authoring"
],
"mustMention": [
"Object",
"MethodHandleCache",
"Impl",
"version"
],
"mustAvoid": [
"publicLookup",
"grantAll=true"
],
"rubric": "Analyse before scaffolding. Keep target types out of the service signature and hand the object across as Object. Use @ExternalType only where the signature is nameable at compile time, and MethodHandleCache for version-variant members because its failures are not cached. Name the implementation <serviceFqcn>Impl or ship a provider declaration."
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
name: btrace-extension-authoring
description: Use when designing a new BTrace extension for a target library, framework, or runtime, deciding how a probe should reach target types safely, or choosing between @ExternalType and hand-written method handles.
---

# Extension Authoring

Start in analysis only. Establish which target entry points are public and stable, which versions
must be supported, and which types a probe actually needs, then say what the extension would look
like before creating any files. Report use of internal or version-sensitive target APIs as a risk
at this point rather than after scaffolding.

Derive the service surface from the probe-facing use cases, because everything downstream follows
from it. Service methods take and return only primitives, `java.*` types, and the extension's own
types; a target object crosses the boundary as `Object` and is resolved inside the implementation.
Keeping target types out of the service signature is what lets the extension load when the target
library is absent or a different version than expected.

Model an individual target type with `@ExternalType`, whose processor emits a companion adapter
with cached method handles. It fits a narrow shape: the whole signature must be nameable at
compile time, so any method that takes or returns a target-library type is out of reach; overloaded
names are rejected outright; fields, constructors, `instanceof`, and non-public members are not
supported; and members of packages a named module does not export are inaccessible. Check a target
method against that list before assuming the annotation can express it.

Note also that static dispatch resolves the owning class through the thread context class loader
while virtual dispatch uses the receiver's defining loader. Prefer virtual dispatch on a handed-off
receiver where a choice exists, because a context loader is whatever the application thread happens
to have set, and is frequently wrong under application servers, OSGi, and plugin loaders. Class
resolution is not cached, so a missing target class throws on every call rather than once, and it
surfaces at the probe call site rather than at load.

Use `ClassLoadingUtil` and `MethodHandleCache` for everything the annotation cannot express, and
for anything that varies across target versions. That cache stores successful lookups and never
stores failures, so catching a lookup failure is a genuine capability check that degrades to a
reduced feature set and recovers if the class appears later. `@ExternalType` offers no such catch
point, so version-variant members belong on the hand-written path.

Nothing in the metadata describes which target versions an extension supports; that is author
discipline. Because binding is per method and per class, a member missing from one version fails
only where it is used. Hand-written stubs of the target API under the test source set give unit
tests realistic fixtures and let a version matrix be exercised without the real dependency.

Name the implementation exactly `<serviceInterfaceFqcn>Impl`, or declare it in
`META-INF/services/<serviceInterfaceFqcn>`. Separating `api` and `impl` packages is reasonable and
requires the provider declaration, because the fallback is an exact name and nothing else. Extend
`Extension` when the service needs the extension context, initialization, or cleanup on detach; a
stateless service is better without it, since instantiation fails when no context is available.

Verify by inspecting the packaged artifacts, installing, attaching, and taking one trace that
exercises the injected service against the real target library rather than a stub.

Packaging mechanics, permission scanning, and the shared project layout are covered by
`btrace-extensions-and-permissions`. Fat agents for deployment are covered by the
`btrace-observability` plugin's startup and packaging guidance.