From 62172d764d859fbbd399b6627cc9307d16373818 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Sat, 1 Nov 2025 07:24:30 -0400 Subject: [PATCH 1/3] start generating weaver files --- docs/instrumentation-list.yaml | 298 ++++++++++++++++++ .../docs/DocGeneratorApplication.java | 3 + .../docs/WeaverModelGenerator.java | 109 +++++++ .../docs/WeaverModelGeneratorTest.java | 91 ++++++ .../models/registry_manifest.yaml | 8 + .../activej-http-6.0/models/signals.yaml | 14 + 6 files changed, 523 insertions(+) create mode 100644 instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/WeaverModelGenerator.java create mode 100644 instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/WeaverModelGeneratorTest.java create mode 100644 instrumentation/activej-http-6.0/models/registry_manifest.yaml create mode 100644 instrumentation/activej-http-6.0/models/signals.yaml diff --git a/docs/instrumentation-list.yaml b/docs/instrumentation-list.yaml index dbdbeb5b44cc..ecabb780fe4e 100644 --- a/docs/instrumentation-list.yaml +++ b/docs/instrumentation-list.yaml @@ -5847,6 +5847,53 @@ libraries: target_versions: javaagent: - redis.clients:jedis:[1.4.0,3.0.0) + telemetry: + - when: default + spans: + - span_kind: CLIENT + attributes: + - name: db.operation + type: STRING + - name: db.statement + type: STRING + - name: db.system + type: STRING + - name: peer.service + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - when: otel.semconv-stability.opt-in=database + metrics: + - name: db.client.operation.duration + description: Duration of database client operations. + type: HISTOGRAM + unit: s + attributes: + - name: db.operation.name + type: STRING + - name: db.system.name + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + spans: + - span_kind: CLIENT + attributes: + - name: db.operation.name + type: STRING + - name: db.query.text + type: STRING + - name: db.system.name + type: STRING + - name: peer.service + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG - name: jedis-3.0 source_path: instrumentation/jedis/jedis-3.0 scope: @@ -5854,6 +5901,69 @@ libraries: target_versions: javaagent: - redis.clients:jedis:[3.0.0,4) + telemetry: + - when: default + spans: + - span_kind: CLIENT + attributes: + - name: db.operation + type: STRING + - name: db.statement + type: STRING + - name: db.system + type: STRING + - name: network.peer.address + type: STRING + - name: network.peer.port + type: LONG + - name: network.type + type: STRING + - name: peer.service + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - when: otel.semconv-stability.opt-in=database + metrics: + - name: db.client.operation.duration + description: Duration of database client operations. + type: HISTOGRAM + unit: s + attributes: + - name: db.operation.name + type: STRING + - name: db.system.name + type: STRING + - name: network.peer.address + type: STRING + - name: network.peer.port + type: LONG + - name: server.address + type: STRING + - name: server.port + type: LONG + spans: + - span_kind: CLIENT + attributes: + - name: db.operation.name + type: STRING + - name: db.query.text + type: STRING + - name: db.system.name + type: STRING + - name: network.peer.address + type: STRING + - name: network.peer.port + type: LONG + - name: network.type + type: STRING + - name: peer.service + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG - name: jedis-4.0 source_path: instrumentation/jedis/jedis-4.0 scope: @@ -5861,6 +5971,53 @@ libraries: target_versions: javaagent: - redis.clients:jedis:[4.0.0-beta1,) + telemetry: + - when: default + spans: + - span_kind: CLIENT + attributes: + - name: db.operation + type: STRING + - name: db.statement + type: STRING + - name: db.system + type: STRING + - name: network.peer.address + type: STRING + - name: network.peer.port + type: LONG + - name: network.type + type: STRING + - when: otel.semconv-stability.opt-in=database + metrics: + - name: db.client.operation.duration + description: Duration of database client operations. + type: HISTOGRAM + unit: s + attributes: + - name: db.operation.name + type: STRING + - name: db.system.name + type: STRING + - name: network.peer.address + type: STRING + - name: network.peer.port + type: LONG + spans: + - span_kind: CLIENT + attributes: + - name: db.operation.name + type: STRING + - name: db.query.text + type: STRING + - name: db.system.name + type: STRING + - name: network.peer.address + type: STRING + - name: network.peer.port + type: LONG + - name: network.type + type: STRING jetty: - name: jetty-11.0 source_path: instrumentation/jetty/jetty-11.0 @@ -5870,6 +6027,53 @@ libraries: target_versions: javaagent: - org.eclipse.jetty:jetty-server:[11, 12) + telemetry: + - when: default + metrics: + - name: http.server.request.duration + description: Duration of HTTP server requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: url.scheme + type: STRING + spans: + - span_kind: SERVER + attributes: + - name: client.address + type: STRING + - name: error.type + type: STRING + - name: http.request.method + type: STRING + - name: http.request.method_original + type: STRING + - name: http.response.status_code + type: LONG + - name: network.peer.address + type: STRING + - name: network.peer.port + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: url.path + type: STRING + - name: url.query + type: STRING + - name: url.scheme + type: STRING + - name: user_agent.original + type: STRING - name: jetty-12.0 source_path: instrumentation/jetty/jetty-12.0 minimum_java_version: 17 @@ -5878,6 +6082,53 @@ libraries: target_versions: javaagent: - org.eclipse.jetty:jetty-server:[12,) + telemetry: + - when: default + metrics: + - name: http.server.request.duration + description: Duration of HTTP server requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: url.scheme + type: STRING + spans: + - span_kind: SERVER + attributes: + - name: client.address + type: STRING + - name: error.type + type: STRING + - name: http.request.method + type: STRING + - name: http.request.method_original + type: STRING + - name: http.response.status_code + type: LONG + - name: network.peer.address + type: STRING + - name: network.peer.port + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: url.path + type: STRING + - name: url.query + type: STRING + - name: url.scheme + type: STRING + - name: user_agent.original + type: STRING - name: jetty-8.0 source_path: instrumentation/jetty/jetty-8.0 scope: @@ -5885,6 +6136,53 @@ libraries: target_versions: javaagent: - org.eclipse.jetty:jetty-server:[8.0.0.v20110901,11) + telemetry: + - when: default + metrics: + - name: http.server.request.duration + description: Duration of HTTP server requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: url.scheme + type: STRING + spans: + - span_kind: SERVER + attributes: + - name: client.address + type: STRING + - name: error.type + type: STRING + - name: http.request.method + type: STRING + - name: http.request.method_original + type: STRING + - name: http.response.status_code + type: LONG + - name: network.peer.address + type: STRING + - name: network.peer.port + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: url.path + type: STRING + - name: url.query + type: STRING + - name: url.scheme + type: STRING + - name: user_agent.original + type: STRING - name: jetty-httpclient-12.0 source_path: instrumentation/jetty-httpclient/jetty-httpclient-12.0 minimum_java_version: 17 diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/DocGeneratorApplication.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/DocGeneratorApplication.java index c86dbec037b4..4cf870e09047 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/DocGeneratorApplication.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/DocGeneratorApplication.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.docs; +import static io.opentelemetry.instrumentation.docs.WeaverModelGenerator.generateWeaverModels; import static java.util.Locale.Category.FORMAT; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; @@ -47,6 +48,8 @@ public static void main(String[] args) throws IOException { YamlHelper.generateInstrumentationYaml(modules, writer); } + generateWeaverModels(modules); + printStats(modules); } diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/WeaverModelGenerator.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/WeaverModelGenerator.java new file mode 100644 index 000000000000..6555a2c5371a --- /dev/null +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/WeaverModelGenerator.java @@ -0,0 +1,109 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.docs; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; +import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; +import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; +import io.opentelemetry.instrumentation.docs.utils.FileManager; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class WeaverModelGenerator { + + private static final Path baseRepoPath = getPath(); + + private static Path getPath() { + String base = System.getProperty("basePath"); + if (base == null || base.isBlank()) { + return Paths.get("."); + } + return Paths.get(base); + } + + public static void main(String[] args) throws IOException { + FileManager fileManager = new FileManager(baseRepoPath.toString()); + List modules = new InstrumentationAnalyzer(fileManager).analyze(); + + generateWeaverModels(modules); + } + + public static void generateWeaverModels(List modules) throws IOException { + for (InstrumentationModule module : modules) { + if (!module.getInstrumentationName().equals("activej-http-6.0")) { + continue; + } + + Path moduleSrc = baseRepoPath.resolve(module.getSrcPath()); + Path modelsDir = moduleSrc.resolve("models"); + Files.createDirectories(modelsDir); + + Path signalsPath = modelsDir.resolve("signals.yaml"); + try (BufferedWriter writer = Files.newBufferedWriter(signalsPath, UTF_8)) { + generateSignals(module, writer); + } + + Path manifestPath = modelsDir.resolve("registry_manifest.yaml"); + try (BufferedWriter writer = Files.newBufferedWriter(manifestPath, UTF_8)) { + generateManifest(module, writer); + } + } + } + + public static void generateSignals(InstrumentationModule module, BufferedWriter writer) + throws IOException { + writer.write("# This file is generated and should not be manually edited.\n"); + writer.write("groups:\n"); + + Map> metricsMap = module.getMetrics(); + if (metricsMap != null && metricsMap.get("default") != null) { + for (EmittedMetrics.Metric metric : metricsMap.get("default")) { + writer.write(" - id: metric." + quote(metric.getName()) + "\n"); + writer.write(" type: metric\n"); + writer.write(" metric_name: " + quote(metric.getName()) + "\n"); + writer.write(" stability: development\n"); + writer.write(" brief: " + quote(metric.getDescription()) + "\n"); + writer.write(" instrument: " + quote(metric.getType().toLowerCase(Locale.ROOT)) + "\n"); + writer.write(" unit: " + quote(metric.getUnit()) + "\n"); + writer.write(" attributes:\n"); + for (TelemetryAttribute attribute : metric.getAttributes()) { + writer.write(" - ref: " + quote(attribute.getName()) + "\n"); + } + } + } + } + + public static void generateManifest(InstrumentationModule module, BufferedWriter writer) + throws IOException { + writer.write("# This file is generated and should not be manually edited.\n"); + writer.write("name: " + quote(module.getInstrumentationName()) + "\n"); + writer.write( + "description: " + quote(module.getInstrumentationName() + " Semantic Conventions") + "\n"); + writer.write("semconv_version: 0.1.0\n"); + writer.write("schema_base_url: https://weaver-example.io/schemas/\n"); + writer.write("dependencies:\n"); + writer.write(" - name: otel\n"); + writer.write( + " registry_path: https://github.com/open-telemetry/semantic-conventions/archive/refs/tags/v1.34.0.zip[model]"); + } + + private static String quote(String s) { + if (s == null) { + return "\"\""; + } + return "\"" + s.replace("\"", "\\\"") + "\""; + } + + private WeaverModelGenerator() {} +} diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/WeaverModelGeneratorTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/WeaverModelGeneratorTest.java new file mode 100644 index 000000000000..70f5302e266e --- /dev/null +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/WeaverModelGeneratorTest.java @@ -0,0 +1,91 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.docs; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; +import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; +import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class WeaverModelGeneratorTest { + + EmittedMetrics.Metric metric = + new EmittedMetrics.Metric( + "http.server.request.duration", + "Duration of HTTP server requests.", + "HISTOGRAM", + "s", + List.of( + new TelemetryAttribute("http.request.method", "STRING"), + new TelemetryAttribute("http.response.status_code", "LONG"), + new TelemetryAttribute("network.protocol.version", "STRING"))); + + InstrumentationModule module = + new InstrumentationModule.Builder() + .namespace("activej") + .group("activej") + .srcPath("instrumentation/activej-http-6.0") + .instrumentationName("activej-http-6.0") + .metrics(Map.of("default", List.of(metric))) + .build(); + + @Test + void testGenerateSignals() throws IOException { + StringWriter stringWriter = new StringWriter(); + BufferedWriter writer = new BufferedWriter(stringWriter); + + WeaverModelGenerator.generateSignals(module, writer); + writer.flush(); + + String expectedYaml = + """ + # This file is generated and should not be manually edited. + groups: + - id: metric."http.server.request.duration" + type: metric + metric_name: "http.server.request.duration" + stability: development + brief: "Duration of HTTP server requests." + instrument: "histogram" + unit: "s" + attributes: + - ref: "http.request.method" + - ref: "http.response.status_code" + - ref: "network.protocol.version" + """; + + assertThat(expectedYaml).isEqualTo(stringWriter.toString()); + } + + @Test + void testGenerateRegistry() throws IOException { + StringWriter stringWriter = new StringWriter(); + BufferedWriter writer = new BufferedWriter(stringWriter); + + WeaverModelGenerator.generateManifest(module, writer); + writer.flush(); + + String expectedYaml = + """ + # This file is generated and should not be manually edited. + name: "activej-http-6.0" + description: "activej-http-6.0 Semantic Conventions" + semconv_version: 0.1.0 + schema_base_url: https://weaver-example.io/schemas/ + dependencies: + - name: otel + registry_path: https://github.com/open-telemetry/semantic-conventions/archive/refs/tags/v1.34.0.zip[model]"""; + + assertThat(expectedYaml).isEqualTo(stringWriter.toString()); + } +} diff --git a/instrumentation/activej-http-6.0/models/registry_manifest.yaml b/instrumentation/activej-http-6.0/models/registry_manifest.yaml new file mode 100644 index 000000000000..f52f4f35b27d --- /dev/null +++ b/instrumentation/activej-http-6.0/models/registry_manifest.yaml @@ -0,0 +1,8 @@ +# This file is generated and should not be manually edited. +name: "activej-http-6.0" +description: "activej-http-6.0 Semantic Conventions" +semconv_version: 0.1.0 +schema_base_url: https://weaver-example.io/schemas/ +dependencies: + - name: otel + registry_path: https://github.com/open-telemetry/semantic-conventions/archive/refs/tags/v1.34.0.zip[model] \ No newline at end of file diff --git a/instrumentation/activej-http-6.0/models/signals.yaml b/instrumentation/activej-http-6.0/models/signals.yaml new file mode 100644 index 000000000000..76fcf2dc5afb --- /dev/null +++ b/instrumentation/activej-http-6.0/models/signals.yaml @@ -0,0 +1,14 @@ +# This file is generated and should not be manually edited. +groups: + - id: metric."http.server.request.duration" + type: metric + metric_name: "http.server.request.duration" + stability: development + brief: "Duration of HTTP server requests." + instrument: "histogram" + unit: "s" + attributes: + - ref: "http.request.method" + - ref: "url.scheme" + - ref: "network.protocol.version" + - ref: "http.response.status_code" From 9fa48a7bed9a49c90274ac40f1ef756fb80e7770 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Sat, 1 Nov 2025 11:15:27 -0400 Subject: [PATCH 2/3] gradle convention for generating weaver docs --- build.gradle.kts | 1 + .../main/kotlin/otel.weaver-docs.gradle.kts | 127 ++++++++ docs/contributing/weaver.md | 70 ++++ docs/instrumentation-list.yaml | 298 ------------------ .../docs/WeaverModelGenerator.java | 55 +++- .../docs/internal/InstrumentationModule.java | 7 + .../docs/WeaverModelGeneratorTest.java | 35 ++ .../activej-http-6.0/docs/signals.md | 48 +++ .../activej-http-6.0/models/attributes.yaml | 11 + .../registry/markdown/signals.md.j2 | 125 ++++++++ .../registry/markdown/weaver.yaml | 10 + 11 files changed, 481 insertions(+), 306 deletions(-) create mode 100644 conventions/src/main/kotlin/otel.weaver-docs.gradle.kts create mode 100644 docs/contributing/weaver.md create mode 100644 instrumentation/activej-http-6.0/docs/signals.md create mode 100644 instrumentation/activej-http-6.0/models/attributes.yaml create mode 100644 weaver-templates/registry/markdown/signals.md.j2 create mode 100644 weaver-templates/registry/markdown/weaver.yaml diff --git a/build.gradle.kts b/build.gradle.kts index e4b68deeb571..c97c3a15467e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,6 +9,7 @@ plugins { id("io.github.gradle-nexus.publish-plugin") id("otel.spotless-conventions") + id("otel.weaver-docs") /* workaround for What went wrong: Could not determine the dependencies of task ':smoke-tests-otel-starter:spring-boot-3.2:bootJar'. diff --git a/conventions/src/main/kotlin/otel.weaver-docs.gradle.kts b/conventions/src/main/kotlin/otel.weaver-docs.gradle.kts new file mode 100644 index 000000000000..f7b013eb38b0 --- /dev/null +++ b/conventions/src/main/kotlin/otel.weaver-docs.gradle.kts @@ -0,0 +1,127 @@ +import java.io.File + +// Weaver documentation generation convention plugin +// Recursively finds all instrumentation modules with models/ directories +// and creates Gradle tasks to generate documentation using the OpenTelemetry Weaver tool + +/** + * Creates a Gradle task for generating weaver documentation from a models directory. + * + * @param instrumentationName The name to use in the task (derived from relative path) + * @param modelsDir The directory containing weaver model files + * @param docsDir The output directory for generated documentation + */ +fun createWeaverDocTask(instrumentationName: String, modelsDir: File, docsDir: File): TaskProvider { + return tasks.register("generateWeaverDocs-${instrumentationName}") { + group = "documentation" + description = "Generate weaver documentation for $instrumentationName instrumentation" + + inputs.dir(modelsDir) + outputs.dir(docsDir) + + standardOutput = System.out + executable = "docker" + + // Run as root in container to avoid permission issues with cache directory + val dockerArgs = listOf() + + val cacheDir = File(project.layout.buildDirectory.get().asFile, ".weaver-cache") + + // Template hierarchy (in order of precedence): + // 1. Module-specific templates: models/templates/ (highest priority) + // 2. Global shared templates: weaver-templates/ (medium priority) + // 3. Default semantic-conventions templates (fallback) + val moduleTemplatesDir = File(modelsDir, "templates") + val globalTemplatesDir = File(project.rootDir, "weaver-templates") + + val templatesSource = when { + moduleTemplatesDir.exists() && moduleTemplatesDir.isDirectory -> { + // Use module-specific templates (no mount needed, already in /source/templates) + "/source/templates" + } + globalTemplatesDir.exists() && globalTemplatesDir.isDirectory -> { + // Use global shared templates (needs separate mount) + "/shared-templates" + } + else -> { + // Fall back to default semantic-conventions templates + "https://github.com/open-telemetry/semantic-conventions/archive/refs/tags/v1.34.0.zip[templates]" + } + } + + // Build mount arguments - add global templates mount if using them + val mountArgs = mutableListOf( + "--mount", "type=bind,source=${modelsDir.absolutePath},target=/source,readonly", + "--mount", "type=bind,source=${docsDir.absolutePath},target=/target", + "--mount", "type=bind,source=${cacheDir.absolutePath},target=/home/weaver/.weaver" + ) + + if (templatesSource == "/shared-templates") { + mountArgs.addAll(listOf( + "--mount", "type=bind,source=${globalTemplatesDir.absolutePath},target=/shared-templates,readonly" + )) + } + + val weaverArgs = listOf( + "otel/weaver:v0.18.0@sha256:5425ade81dc22ddd840902b0638b4b6a9186fb654c5b50c1d1ccd31299437390", + "registry", "generate", + "--registry=/source", + "--templates=${templatesSource}", + "markdown", "/target" + ) + + args = listOf("run", "--rm", "--platform=linux/x86_64") + dockerArgs + mountArgs + weaverArgs + + doFirst { + if (!modelsDir.exists()) { + throw GradleException("Models directory does not exist: ${modelsDir.absolutePath}") + } + docsDir.mkdirs() + cacheDir.mkdirs() + } + } +} + +/** + * Recursively searches for all models/ directories under a given directory. + * + * @param dir The directory to search + * @param baseDir The base directory for calculating relative paths + * @return List of pairs containing (task-suffix, module-directory) + */ +fun findModelsDirectories(dir: File, baseDir: File = dir): List> { + val results = mutableListOf>() + + dir.listFiles()?.forEach { file -> + if (file.isDirectory) { + val modelsDir = File(file, "models") + if (modelsDir.exists() && modelsDir.isDirectory) { + val relativePath = file.relativeTo(baseDir).path.replace(File.separatorChar, '-') + results.add(Pair(relativePath, file)) + } + // Recursively search subdirectories + results.addAll(findModelsDirectories(file, baseDir)) + } + } + + return results +} + +// Find all instrumentation modules with models directories and register tasks +val weaverDocTasks = mutableListOf>() +val instrumentationDir = file("instrumentation") +if (instrumentationDir.exists() && instrumentationDir.isDirectory) { + findModelsDirectories(instrumentationDir).forEach { (taskSuffix, moduleDir) -> + val modelsDir = File(moduleDir, "models") + val docsDir = File(moduleDir, "docs") + val taskProvider = createWeaverDocTask(taskSuffix, modelsDir, docsDir) + weaverDocTasks.add(taskProvider) + } +} + +// Create an aggregate task to generate all weaver docs +tasks.register("generateAllWeaverDocs") { + group = "documentation" + description = "Generate weaver documentation for all instrumentation modules" + dependsOn(weaverDocTasks) +} diff --git a/docs/contributing/weaver.md b/docs/contributing/weaver.md new file mode 100644 index 000000000000..7fae547b7408 --- /dev/null +++ b/docs/contributing/weaver.md @@ -0,0 +1,70 @@ +# Weaver Documentation Generation Guide + +This project now includes Gradle tasks for generating documentation from weaver model files using +the OpenTelemetry Weaver tool. The model files are currently generated by the gradle task +`./gradlew :instrumentation-docs:runAnalysis`. + +The build system automatically finds all instrumentation modules with a `models/` directory and +creates Gradle tasks to generate documentation into a corresponding `docs/` directory. + +## Model Directory Structure + +Each instrumentation with weaver support will have: + +``` +instrumentation// +├── models/ # Scaffolded / Generated by the DocGenerater task +│ ├── registry_manifest.yaml # Registry metadata and dependencies +│ ├── signals.yaml # Metrics and spans definitions +│ └── attributes.yaml # (Optional) Attribute group definitions +└── docs/ # Generated documentation goes here + └── README.md # Auto-generated +``` + +## Usage + +### Generate Documentation for a Specific Module + +```bash +./gradlew generateWeaverDocs- + +# Example: +./gradlew generateWeaverDocs-activej-http-6.0 +``` + +### Generate Documentation for All Modules + +```bash +./gradlew generateAllWeaverDocs +``` + +### List Available Documentation Tasks + +```bash +./gradlew tasks --group=documentation +``` + +## Requirements + +- Docker must be installed and running +- The otel/weaver docker image will be pulled automatically + +## Output + +Generated documentation will appear in: +- `instrumentation//docs/README.md` + +## Manually Adding Weaver Support to a New Module + +1. Create a `models/` directory in your instrumentation module +2. Add `registry_manifest.yaml` with metadata and dependencies +3. Add `signals.yaml` to define metrics/spans +4. Add `attributes.yaml` for attributes +5. Run `./gradlew generateWeaverDocs-` + +The build system will automatically detect the new models directory and create the task. + +## Further Reading + +- [Weaver Documentation](https://github.com/open-telemetry/weaver/tree/main/docs) +- [Weaver Example Project](https://github.com/jerbly/weaver-example) diff --git a/docs/instrumentation-list.yaml b/docs/instrumentation-list.yaml index ecabb780fe4e..dbdbeb5b44cc 100644 --- a/docs/instrumentation-list.yaml +++ b/docs/instrumentation-list.yaml @@ -5847,53 +5847,6 @@ libraries: target_versions: javaagent: - redis.clients:jedis:[1.4.0,3.0.0) - telemetry: - - when: default - spans: - - span_kind: CLIENT - attributes: - - name: db.operation - type: STRING - - name: db.statement - type: STRING - - name: db.system - type: STRING - - name: peer.service - type: STRING - - name: server.address - type: STRING - - name: server.port - type: LONG - - when: otel.semconv-stability.opt-in=database - metrics: - - name: db.client.operation.duration - description: Duration of database client operations. - type: HISTOGRAM - unit: s - attributes: - - name: db.operation.name - type: STRING - - name: db.system.name - type: STRING - - name: server.address - type: STRING - - name: server.port - type: LONG - spans: - - span_kind: CLIENT - attributes: - - name: db.operation.name - type: STRING - - name: db.query.text - type: STRING - - name: db.system.name - type: STRING - - name: peer.service - type: STRING - - name: server.address - type: STRING - - name: server.port - type: LONG - name: jedis-3.0 source_path: instrumentation/jedis/jedis-3.0 scope: @@ -5901,69 +5854,6 @@ libraries: target_versions: javaagent: - redis.clients:jedis:[3.0.0,4) - telemetry: - - when: default - spans: - - span_kind: CLIENT - attributes: - - name: db.operation - type: STRING - - name: db.statement - type: STRING - - name: db.system - type: STRING - - name: network.peer.address - type: STRING - - name: network.peer.port - type: LONG - - name: network.type - type: STRING - - name: peer.service - type: STRING - - name: server.address - type: STRING - - name: server.port - type: LONG - - when: otel.semconv-stability.opt-in=database - metrics: - - name: db.client.operation.duration - description: Duration of database client operations. - type: HISTOGRAM - unit: s - attributes: - - name: db.operation.name - type: STRING - - name: db.system.name - type: STRING - - name: network.peer.address - type: STRING - - name: network.peer.port - type: LONG - - name: server.address - type: STRING - - name: server.port - type: LONG - spans: - - span_kind: CLIENT - attributes: - - name: db.operation.name - type: STRING - - name: db.query.text - type: STRING - - name: db.system.name - type: STRING - - name: network.peer.address - type: STRING - - name: network.peer.port - type: LONG - - name: network.type - type: STRING - - name: peer.service - type: STRING - - name: server.address - type: STRING - - name: server.port - type: LONG - name: jedis-4.0 source_path: instrumentation/jedis/jedis-4.0 scope: @@ -5971,53 +5861,6 @@ libraries: target_versions: javaagent: - redis.clients:jedis:[4.0.0-beta1,) - telemetry: - - when: default - spans: - - span_kind: CLIENT - attributes: - - name: db.operation - type: STRING - - name: db.statement - type: STRING - - name: db.system - type: STRING - - name: network.peer.address - type: STRING - - name: network.peer.port - type: LONG - - name: network.type - type: STRING - - when: otel.semconv-stability.opt-in=database - metrics: - - name: db.client.operation.duration - description: Duration of database client operations. - type: HISTOGRAM - unit: s - attributes: - - name: db.operation.name - type: STRING - - name: db.system.name - type: STRING - - name: network.peer.address - type: STRING - - name: network.peer.port - type: LONG - spans: - - span_kind: CLIENT - attributes: - - name: db.operation.name - type: STRING - - name: db.query.text - type: STRING - - name: db.system.name - type: STRING - - name: network.peer.address - type: STRING - - name: network.peer.port - type: LONG - - name: network.type - type: STRING jetty: - name: jetty-11.0 source_path: instrumentation/jetty/jetty-11.0 @@ -6027,53 +5870,6 @@ libraries: target_versions: javaagent: - org.eclipse.jetty:jetty-server:[11, 12) - telemetry: - - when: default - metrics: - - name: http.server.request.duration - description: Duration of HTTP server requests. - type: HISTOGRAM - unit: s - attributes: - - name: http.request.method - type: STRING - - name: http.response.status_code - type: LONG - - name: network.protocol.version - type: STRING - - name: url.scheme - type: STRING - spans: - - span_kind: SERVER - attributes: - - name: client.address - type: STRING - - name: error.type - type: STRING - - name: http.request.method - type: STRING - - name: http.request.method_original - type: STRING - - name: http.response.status_code - type: LONG - - name: network.peer.address - type: STRING - - name: network.peer.port - type: LONG - - name: network.protocol.version - type: STRING - - name: server.address - type: STRING - - name: server.port - type: LONG - - name: url.path - type: STRING - - name: url.query - type: STRING - - name: url.scheme - type: STRING - - name: user_agent.original - type: STRING - name: jetty-12.0 source_path: instrumentation/jetty/jetty-12.0 minimum_java_version: 17 @@ -6082,53 +5878,6 @@ libraries: target_versions: javaagent: - org.eclipse.jetty:jetty-server:[12,) - telemetry: - - when: default - metrics: - - name: http.server.request.duration - description: Duration of HTTP server requests. - type: HISTOGRAM - unit: s - attributes: - - name: http.request.method - type: STRING - - name: http.response.status_code - type: LONG - - name: network.protocol.version - type: STRING - - name: url.scheme - type: STRING - spans: - - span_kind: SERVER - attributes: - - name: client.address - type: STRING - - name: error.type - type: STRING - - name: http.request.method - type: STRING - - name: http.request.method_original - type: STRING - - name: http.response.status_code - type: LONG - - name: network.peer.address - type: STRING - - name: network.peer.port - type: LONG - - name: network.protocol.version - type: STRING - - name: server.address - type: STRING - - name: server.port - type: LONG - - name: url.path - type: STRING - - name: url.query - type: STRING - - name: url.scheme - type: STRING - - name: user_agent.original - type: STRING - name: jetty-8.0 source_path: instrumentation/jetty/jetty-8.0 scope: @@ -6136,53 +5885,6 @@ libraries: target_versions: javaagent: - org.eclipse.jetty:jetty-server:[8.0.0.v20110901,11) - telemetry: - - when: default - metrics: - - name: http.server.request.duration - description: Duration of HTTP server requests. - type: HISTOGRAM - unit: s - attributes: - - name: http.request.method - type: STRING - - name: http.response.status_code - type: LONG - - name: network.protocol.version - type: STRING - - name: url.scheme - type: STRING - spans: - - span_kind: SERVER - attributes: - - name: client.address - type: STRING - - name: error.type - type: STRING - - name: http.request.method - type: STRING - - name: http.request.method_original - type: STRING - - name: http.response.status_code - type: LONG - - name: network.peer.address - type: STRING - - name: network.peer.port - type: LONG - - name: network.protocol.version - type: STRING - - name: server.address - type: STRING - - name: server.port - type: LONG - - name: url.path - type: STRING - - name: url.query - type: STRING - - name: url.scheme - type: STRING - - name: user_agent.original - type: STRING - name: jetty-httpclient-12.0 source_path: instrumentation/jetty-httpclient/jetty-httpclient-12.0 minimum_java_version: 17 diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/WeaverModelGenerator.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/WeaverModelGenerator.java index 6555a2c5371a..8ab3b44e914d 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/WeaverModelGenerator.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/WeaverModelGenerator.java @@ -10,7 +10,6 @@ import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; -import io.opentelemetry.instrumentation.docs.utils.FileManager; import java.io.BufferedWriter; import java.io.IOException; import java.nio.file.Files; @@ -19,6 +18,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; public class WeaverModelGenerator { @@ -32,13 +32,6 @@ private static Path getPath() { return Paths.get(base); } - public static void main(String[] args) throws IOException { - FileManager fileManager = new FileManager(baseRepoPath.toString()); - List modules = new InstrumentationAnalyzer(fileManager).analyze(); - - generateWeaverModels(modules); - } - public static void generateWeaverModels(List modules) throws IOException { for (InstrumentationModule module : modules) { if (!module.getInstrumentationName().equals("activej-http-6.0")) { @@ -58,6 +51,14 @@ public static void generateWeaverModels(List modules) thr try (BufferedWriter writer = Files.newBufferedWriter(manifestPath, UTF_8)) { generateManifest(module, writer); } + + Set attributes = getMetricAttributes(module); + if (!attributes.isEmpty()) { + Path attributesPath = modelsDir.resolve("attributes.yaml"); + try (BufferedWriter writer = Files.newBufferedWriter(attributesPath, UTF_8)) { + generateAttributes(module, writer, attributes); + } + } } } @@ -98,6 +99,44 @@ public static void generateManifest(InstrumentationModule module, BufferedWriter " registry_path: https://github.com/open-telemetry/semantic-conventions/archive/refs/tags/v1.34.0.zip[model]"); } + public static void generateAttributes( + InstrumentationModule module, BufferedWriter writer, Set attributes) + throws IOException { + writer.write("# This file is generated and should not be manually edited.\n"); + writer.write("groups:\n"); + writer.write(" - id: registry." + module.getInstrumentationName() + "\n"); + writer.write(" type: attribute_group\n"); + writer.write(" display_name: " + module.getResolvedName() + " Attributes\n"); + writer.write( + " brief: Attributes captured by " + module.getResolvedName() + " instrumentation.\n"); + writer.write(" attributes:\n"); + for (String attribute : attributes) { + writer.write(" - ref: " + attribute + "\n"); + } + } + + /** + * Get all metric attributes used by the given module. Sorted and deduplicated. + * + * @param module the instrumentation module + * @return set of attribute names + */ + // visible for testing + public static Set getMetricAttributes(InstrumentationModule module) { + Set attributes = new java.util.TreeSet<>(); + if (module.getMetrics() != null && module.getMetrics().get("default") != null) { + for (EmittedMetrics.Metric metric : module.getMetrics().get("default")) { + for (TelemetryAttribute attribute : metric.getAttributes()) { + String name = attribute.getName(); + if (name != null) { + attributes.add(name); + } + } + } + } + return attributes; + } + private static String quote(String s) { if (s == null) { return "\"\""; diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationModule.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationModule.java index 1b13cb8eb55e..db0f3c8c524a 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationModule.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationModule.java @@ -67,6 +67,13 @@ public String getInstrumentationName() { return instrumentationName; } + public String getResolvedName() { + if (metadata != null && metadata.getDisplayName() != null) { + return metadata.getDisplayName(); + } + return instrumentationName; + } + public String getNamespace() { return namespace; } diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/WeaverModelGeneratorTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/WeaverModelGeneratorTest.java index 70f5302e266e..d88be19771cf 100644 --- a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/WeaverModelGeneratorTest.java +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/WeaverModelGeneratorTest.java @@ -8,6 +8,7 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; +import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetadata; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; import java.io.BufferedWriter; @@ -15,6 +16,7 @@ import java.io.StringWriter; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.Test; class WeaverModelGeneratorTest { @@ -28,14 +30,19 @@ class WeaverModelGeneratorTest { List.of( new TelemetryAttribute("http.request.method", "STRING"), new TelemetryAttribute("http.response.status_code", "LONG"), + new TelemetryAttribute("url.scheme", "STRING"), new TelemetryAttribute("network.protocol.version", "STRING"))); + InstrumentationMetadata metadata = + new InstrumentationMetadata.Builder().displayName("ActiveJ").build(); + InstrumentationModule module = new InstrumentationModule.Builder() .namespace("activej") .group("activej") .srcPath("instrumentation/activej-http-6.0") .instrumentationName("activej-http-6.0") + .metadata(metadata) .metrics(Map.of("default", List.of(metric))) .build(); @@ -61,6 +68,7 @@ void testGenerateSignals() throws IOException { attributes: - ref: "http.request.method" - ref: "http.response.status_code" + - ref: "url.scheme" - ref: "network.protocol.version" """; @@ -88,4 +96,31 @@ void testGenerateRegistry() throws IOException { assertThat(expectedYaml).isEqualTo(stringWriter.toString()); } + + @Test + void testGenerateAttributes() throws IOException { + StringWriter stringWriter = new StringWriter(); + BufferedWriter writer = new BufferedWriter(stringWriter); + + Set attributes = WeaverModelGenerator.getMetricAttributes(module); + WeaverModelGenerator.generateAttributes(module, writer, attributes); + writer.flush(); + + String expectedYaml = + """ + # This file is generated and should not be manually edited. + groups: + - id: registry.activej-http-6.0 + type: attribute_group + display_name: ActiveJ Attributes + brief: Attributes captured by ActiveJ instrumentation. + attributes: + - ref: http.request.method + - ref: http.response.status_code + - ref: network.protocol.version + - ref: url.scheme + """; + + assertThat(expectedYaml).isEqualTo(stringWriter.toString()); + } } diff --git a/instrumentation/activej-http-6.0/docs/signals.md b/instrumentation/activej-http-6.0/docs/signals.md new file mode 100644 index 000000000000..f2f4d66efb5d --- /dev/null +++ b/instrumentation/activej-http-6.0/docs/signals.md @@ -0,0 +1,48 @@ + + + +# Signals + +This document describes the telemetry signals (metrics and spans) and attributes emitted by this instrumentation. + +## Metrics + +### `http.server.request.duration` + +Duration of HTTP server requests. + +| Property | Value | +|---|---| +| **Instrument** | histogram | +| **Unit** | `s` | +| **Stability** | ![Development](https://img.shields.io/badge/-development-blue) | + +**Attributes:** + +| Attribute | Requirement Level | +|---|---| +| `http.request.method` | recommended | +| `http.response.status_code` | recommended | +| `network.protocol.version` | recommended | +| `url.scheme` | recommended | + +See the [Attributes](#attributes) section below for detailed attribute definitions. + +--- + +No spans are defined for this instrumentation. + +## Attributes + +### ActiveJ Attributes + +Attributes captured by ActiveJ instrumentation. + +| Attribute | Type | Description | Examples | Stability | +|---|---|---|---|---| +| `http.request.method` | enum | HTTP request method. |GET, POST, HEAD | ![Stable](https://img.shields.io/badge/-stable-lightgreen) | +| `http.response.status_code` | int | [HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6). |200 | ![Stable](https://img.shields.io/badge/-stable-lightgreen) | +| `network.protocol.version` | string | The actual version of the protocol used for network communication. |1.1, 2 | ![Stable](https://img.shields.io/badge/-stable-lightgreen) | +| `url.scheme` | string | The [URI scheme](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) component identifying the used protocol. |https, ftp, telnet | ![Stable](https://img.shields.io/badge/-stable-lightgreen) | + +--- diff --git a/instrumentation/activej-http-6.0/models/attributes.yaml b/instrumentation/activej-http-6.0/models/attributes.yaml new file mode 100644 index 000000000000..28d2003ca689 --- /dev/null +++ b/instrumentation/activej-http-6.0/models/attributes.yaml @@ -0,0 +1,11 @@ +# This file is generated and should not be manually edited. +groups: + - id: registry.activej-http-6.0 + type: attribute_group + display_name: ActiveJ Attributes + brief: Attributes captured by ActiveJ instrumentation. + attributes: + - ref: http.request.method + - ref: http.response.status_code + - ref: network.protocol.version + - ref: url.scheme diff --git a/weaver-templates/registry/markdown/signals.md.j2 b/weaver-templates/registry/markdown/signals.md.j2 new file mode 100644 index 000000000000..f6c26546f491 --- /dev/null +++ b/weaver-templates/registry/markdown/signals.md.j2 @@ -0,0 +1,125 @@ + + + +# Signals + +This document describes the telemetry signals (metrics and spans) and attributes emitted by this instrumentation. + +{%- set metrics = ctx.groups | selectattr("type", "equalto", "metric") | list %} +{%- set spans = ctx.groups | selectattr("type", "equalto", "span") | list %} +{%- set attribute_groups = ctx.groups | selectattr("type", "equalto", "attribute_group") | list %} + +{%- if metrics %} + +## Metrics + +{%- for metric in metrics %} + +### `{{ metric.metric_name }}` + +{{ metric.brief }} + +| Property | Value | +|---|---| +| **Instrument** | {{ metric.instrument }} | +{%- if metric.unit %} +| **Unit** | `{{ metric.unit }}` | +{%- endif %} +| **Stability** | {% if metric.stability == "stable" %}![Stable](https://img.shields.io/badge/-stable-lightgreen){% elif metric.stability == "experimental" %}![Experimental](https://img.shields.io/badge/-experimental-blue){% else %}![Development](https://img.shields.io/badge/-development-blue){% endif %} | + +{%- if metric.note %} + +{{ metric.note }} +{%- endif %} + +{%- if metric.attributes %} + +**Attributes:** + +| Attribute | Requirement Level | +|---|---| +{%- for attribute in metric.attributes %} +| `{{ attribute.name }}` | {{ attribute.requirement_level | default('recommended') }} | +{%- endfor %} + +See the [Attributes](#attributes) section below for detailed attribute definitions. + +{%- endif %} + +--- +{%- endfor %} +{%- else %} + +No metrics are defined for this instrumentation. + +{%- endif %} + +{%- if spans %} + +## Spans + +{%- for span in spans %} + +### Span: `{{ span.span_name | default(span.id | split('.') | last) }}` + +{{ span.brief }} + +| Property | Value | +|---|---| +| **Span Kind** | `{{ span.span_kind }}` | +| **Stability** | {% if span.stability == "stable" %}![Stable](https://img.shields.io/badge/-stable-lightgreen){% elif span.stability == "experimental" %}![Experimental](https://img.shields.io/badge/-experimental-blue){% else %}![Development](https://img.shields.io/badge/-development-blue){% endif %} | + +{%- if span.note %} + +{{ span.note }} +{%- endif %} + +{%- if span.attributes %} + +**Attributes:** + +| Attribute | Requirement Level | +|---|---| +{%- for attribute in span.attributes %} +| `{{ attribute.name }}` | {{ attribute.requirement_level | default('recommended') }} | +{%- endfor %} + +See the [Attributes](#attributes) section below for detailed attribute definitions. + +{%- endif %} + +--- +{%- endfor %} +{%- else %} + +No spans are defined for this instrumentation. + +{%- endif %} + +{%- if attribute_groups %} + +## Attributes + +{%- for group in attribute_groups %} + +### {{ group.display_name | default(group.id) }} + +{{ group.brief }} + +{%- if group.attributes %} + +| Attribute | Type | Description | Examples | Stability | +|---|---|---|---|---| +{%- for attr in group.attributes %} +| `{{ attr.name }}` | {% if attr.type.members %}enum{% else %}{{ attr.type | default('string') }}{% endif %} | {{ attr.brief | default('') | trim }} | {%- if attr.examples %}{{ attr.examples | join(', ') }}{% else %}-{% endif %} | {% if attr.stability == "stable" %}![Stable](https://img.shields.io/badge/-stable-lightgreen){% elif attr.stability == "experimental" %}![Experimental](https://img.shields.io/badge/-experimental-blue){% else %}![Development](https://img.shields.io/badge/-development-blue){% endif %} | +{%- endfor %} + +{%- endif %} + +--- +{%- endfor %} +{%- else %} + +No attribute groups are defined for this instrumentation. + +{%- endif %} \ No newline at end of file diff --git a/weaver-templates/registry/markdown/weaver.yaml b/weaver-templates/registry/markdown/weaver.yaml new file mode 100644 index 000000000000..1644545f63ae --- /dev/null +++ b/weaver-templates/registry/markdown/weaver.yaml @@ -0,0 +1,10 @@ +# Simple signals-only documentation generator +# This generates a signals.md page showing metrics and spans +# Attributes are handled by the default semantic-conventions templates + +templates: + # Generate signals documentation (metrics and spans) + - pattern: signals.md.j2 + filter: '.' + application_mode: single + file_name: signals.md From 18dfedf01c765350fcdefc9bd4b02f70326d891e Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Sat, 1 Nov 2025 13:07:46 -0400 Subject: [PATCH 3/3] fix doc --- docs/contributing/weaver.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/contributing/weaver.md b/docs/contributing/weaver.md index 7fae547b7408..27fb2c71720b 100644 --- a/docs/contributing/weaver.md +++ b/docs/contributing/weaver.md @@ -16,9 +16,9 @@ instrumentation// ├── models/ # Scaffolded / Generated by the DocGenerater task │ ├── registry_manifest.yaml # Registry metadata and dependencies │ ├── signals.yaml # Metrics and spans definitions -│ └── attributes.yaml # (Optional) Attribute group definitions +│ └── attributes.yaml # Attribute group definitions └── docs/ # Generated documentation goes here - └── README.md # Auto-generated + └── signals.md # Auto-generated ``` ## Usage @@ -52,7 +52,7 @@ instrumentation// ## Output Generated documentation will appear in: -- `instrumentation//docs/README.md` +- `instrumentation//docs/signals.md` ## Manually Adding Weaver Support to a New Module