|
1 | 1 | # Muzzle |
2 | 2 |
|
3 | | -Muzzle is a feature of the Java agent that ensures API compatibility |
4 | | -between libraries/symbols on the application classpath and APIs of instrumented |
5 | | -3rd party libraries used by the Agent. In other words the Muzzle ensures |
6 | | -that the API symbols used by the Agent are compatible with API symbols |
7 | | -on the application classpath. The Muzzle will prevent loading an instrumentation |
8 | | -if the APIs do not match. |
| 3 | +Muzzle is a safety feature of the Java agent that prevents applying instrumentation when a mismatch |
| 4 | +between the instrumentation code and the instrumented application code is detected. |
| 5 | +It ensures API compatibility between symbols (classes, methods, fields) on the application classpath |
| 6 | +and references to those symbols made by instrumentation advices defined in the agent. |
| 7 | +In other words, muzzle ensures that the API symbols used by the agent are compatible with the API |
| 8 | +symbols on the application classpath. |
9 | 9 |
|
10 | | -## How does it work |
| 10 | +Muzzle will prevent loading an instrumentation if it detects any mismatch or conflict. |
11 | 11 |
|
12 | | -At build time, for each instrumentation the Muzzle ByteBuddy plugin collects symbols referring to both internal |
13 | | -and 3rd party APIs used by the currently processed instrumentation. The reference collection process starts |
14 | | -from advice classes - values of the map returned by the `Instrumenter.Default#transformers()` method. |
| 12 | +## How it works |
15 | 13 |
|
16 | | -All those references are then used to create a `ReferenceMatcher` instance. |
17 | | -The matcher is stored in the instrumentation class in method `ReferenceMatcher getInstrumentationMuzzle()`. |
| 14 | +Muzzle has two phases: |
| 15 | +* at compile time it collects references to the third-party symbols; |
| 16 | +* at runtime it compares those references to the actual API symbols on the classpath. |
18 | 17 |
|
19 | | -At runtime the Muzzle checks API compatibility between symbols used by the Agent |
20 | | -and symbols in the application class loader. If the symbols do not match the instrumentation is not loaded. |
21 | | -Because the muzzle matcher is expensive, it is only performed after a match has been made by the |
22 | | -`SomeInstrumentation.classLoaderMatcher()` and `SomeInstrumentation.typeMatcher()` matchers. |
| 18 | +### Compile-time reference collection |
| 19 | + |
| 20 | +The compile-time reference collection and code generation process is implemented using a ByteBuddy |
| 21 | +plugin (called `MuzzleCodeGenerationPlugin`). |
| 22 | + |
| 23 | +For each instrumentation the ByteBuddy plugin collects symbols referring to both internal and third |
| 24 | +party APIs used by the currently processed instrumentation. The reference collection process starts |
| 25 | +from advice classes (values of the map returned by the `Instrumenter.Default#transformers()` method) |
| 26 | +and traverses the class graph until it encounters a reference to a non-instrumentation class. |
| 27 | + |
| 28 | +All collected references are then used to create a `ReferenceMatcher` instance. This matcher |
| 29 | +is stored in the instrumentation class in the method `Instrumenter.Default#getMuzzleReferenceMatcher()`. |
| 30 | +The bytecode of this method (basically an array of `Reference` builder calls) is generated |
| 31 | +automatically by the ByteBuddy plugin using an ASM code visitor. |
| 32 | + |
| 33 | +The source code of the compile-time plugin is located in the `javaagent-tooling` module, |
| 34 | +package `io.opentelemetry.javaagent.tooling.muzzle.collector`. |
| 35 | + |
| 36 | +### Runtime reference matching |
| 37 | + |
| 38 | +The runtime reference matching process is implemented as a ByteBuddy matcher in `Instrumenter.Default`. |
| 39 | +`MuzzleMatcher` uses the `getMuzzleReferenceMatcher()` method generated during the compilation phase |
| 40 | +to verify that the class loader of the instrumented type has all necessary symbols (classes, |
| 41 | +methods, fields). If the `ReferenceMatcher` finds any mismatch between collected references and the |
| 42 | +actual application classpath types the whole instrumentation is discarded. |
| 43 | + |
| 44 | +It is worth noting that because the muzzle check is expensive, it is only performed after a match |
| 45 | +has been made by the `InstrumenterDefault#classLoaderMatcher()` and `Instrumenter.Default#typeMatcher()` |
| 46 | +matchers. |
| 47 | + |
| 48 | +The source code of the runtime muzzle matcher is located in the `javaagent-tooling` module, |
| 49 | +in the class `Instrumenter.Default` and under the package `io.opentelemetry.javaagent.tooling.muzzle`. |
23 | 50 |
|
24 | 51 | ## Muzzle gradle plugin |
25 | 52 |
|
26 | | -The `printReferences` task prints all API references in a given module. |
| 53 | +The muzzle gradle plugin allows to perform the runtime reference matching process against different |
| 54 | +third party library versions, when the project is built. |
27 | 55 |
|
28 | | -```bash |
29 | | -./gradlew :instrumentation:google-http-client-1.19:printReferences |
30 | | -``` |
| 56 | +Muzzle gradle plugin is just an additional utility for enhanced build-time checking |
| 57 | +to alert us when there are breaking changes in the underlying third party library |
| 58 | +that will cause the instrumentation not to get applied. |
| 59 | +**Even without using it muzzle reference matching is _always_ active in runtime**, |
| 60 | +it's not an optional feature. |
| 61 | + |
| 62 | +The gradle plugin defines two tasks: |
| 63 | + |
| 64 | +* `muzzle` task runs the runtime muzzle verification against different library versions: |
| 65 | + ```sh |
| 66 | + ./gradlew :instrumentation:google-http-client-1.19:muzzle |
| 67 | + ``` |
| 68 | + If a new, incompatible version of the instrumented library is published it fails the build. |
| 69 | + |
| 70 | +* `printReferences` task prints all API references in a given module: |
| 71 | + ```sh |
| 72 | + ./gradlew :instrumentation:google-http-client-1.19:printReferences |
| 73 | + ``` |
31 | 74 |
|
32 | | -The `muzzle` task downloads 3rd party libraries from maven central and checks API compatibility. |
33 | | -If a new incompatible version is published it fails the build. |
| 75 | +The muzzle plugin needs to be configured in the module's `.gradle` file. |
| 76 | +Example: |
34 | 77 |
|
35 | | -```bash |
36 | | -./gradlew :instrumentation:google-http-client-1.19:muzzle |
| 78 | +```groovy |
| 79 | +muzzle { |
| 80 | + // it is expected that muzzle fails the runtime check for this component |
| 81 | + fail { |
| 82 | + group = "commons-httpclient" |
| 83 | + module = "commons-httpclient" |
| 84 | + // versions from this range are checked |
| 85 | + versions = "[,4.0)" |
| 86 | + // this version is not checked by muzzle |
| 87 | + skipVersions += '3.1-jenkins-1' |
| 88 | + } |
| 89 | + // it is expected that muzzle passes the runtime check for this component |
| 90 | + pass { |
| 91 | + group = "org.apache.httpcomponents" |
| 92 | + module = "httpclient" |
| 93 | + versions = "[4.0,)" |
| 94 | + // verify that all other versions - [,4.0) in this case - fail the muzzle runtime check |
| 95 | + assertInverse = true |
| 96 | + } |
| 97 | + // ... |
| 98 | +} |
37 | 99 | ``` |
38 | 100 |
|
39 | | -## Muzzle location |
| 101 | +* Using either `pass` or `fail` directive allows to specify whether muzzle should treat the |
| 102 | + reference check failure as expected behavior; |
| 103 | +* `versions` is a version range, where `[]` is inclusive and `()` is exclusive. It is not needed to |
| 104 | + specify the exact version to start/end, e.g. `[1.0.0,4)` would usually behave in the same way as |
| 105 | + `[1.0.0,4.0.0-Alpha)`; |
| 106 | +* `assertInverse` is basically a shortcut for adding an opposite directive for all library versions |
| 107 | + that are not included in the specified `versions` range. |
40 | 108 |
|
41 | | -* `buildSrc` - Muzzle Gradle plugin |
42 | | -* `agent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/muzzle` - Muzzle ByteBuddy plugin |
| 109 | +The source code of the gradle plugin is located in the `buildSrc` directory. |
0 commit comments