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..27fb2c71720b --- /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 # Attribute group definitions +└── docs/ # Generated documentation goes here + └── signals.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/signals.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/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..8ab3b44e914d --- /dev/null +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/WeaverModelGenerator.java @@ -0,0 +1,148 @@ +/* + * 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 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; +import java.util.Set; + +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 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); + } + + 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); + } + } + } + } + + 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]"); + } + + 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 "\"\""; + } + return "\"" + s.replace("\"", "\\\"") + "\""; + } + + private WeaverModelGenerator() {} +} 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 new file mode 100644 index 000000000000..d88be19771cf --- /dev/null +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/WeaverModelGeneratorTest.java @@ -0,0 +1,126 @@ +/* + * 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.InstrumentationMetadata; +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 java.util.Set; +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("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(); + + @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: "url.scheme" + - 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()); + } + + @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/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" 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