Skip to content

Commit 3093a4f

Browse files
authored
Create nightly documentation site audit (#14312)
1 parent 09e2ff0 commit 3093a4f

File tree

5 files changed

+404
-0
lines changed

5 files changed

+404
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: opentelemetry.io documentation disable list audit
2+
3+
on:
4+
schedule:
5+
- cron: "30 1 * * *" # daily at 1:30 UTC
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
crawl:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
16+
17+
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
18+
with:
19+
distribution: temurin
20+
java-version: 17
21+
22+
- name: Set up gradle
23+
uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
24+
25+
- name: Run instrumentation analyzer (identify any module changes)
26+
run: ./gradlew :instrumentation-docs:runAnalysis
27+
28+
- name: Run doc site audit
29+
run: ./gradlew :instrumentation-docs:docSiteAudit
30+
31+
workflow-notification:
32+
permissions:
33+
contents: read
34+
issues: write
35+
needs:
36+
- crawl
37+
if: always()
38+
uses: ./.github/workflows/reusable-workflow-notification.yml
39+
with:
40+
success: ${{ needs.crawl.result == 'success' }}

instrumentation-docs/build.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,12 @@ tasks {
2626
mainClass.set("io.opentelemetry.instrumentation.docs.DocGeneratorApplication")
2727
classpath(sourceSets["main"].runtimeClasspath)
2828
}
29+
30+
val docSiteAudit by registering(JavaExec::class) {
31+
dependsOn(classes)
32+
33+
systemProperty("basePath", project.rootDir)
34+
mainClass.set("io.opentelemetry.instrumentation.docs.DocSynchronization")
35+
classpath(sourceSets["main"].runtimeClasspath)
36+
}
2937
}

instrumentation-docs/readme.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,17 @@ data will be excluded from git and just generated on demand.
196196

197197
Each file has a `when` value along with the list of metrics that indicates whether the telemetry is
198198
emitted by default or via a configuration option.
199+
200+
## Doc Synchronization
201+
202+
The documentation site has a section that lists all the instrumentations in the context of
203+
documenting how to disable them.
204+
205+
We have a class `DocSynchronization` that runs a check against our instrumentation-list.yaml file to
206+
identify when we have missing entries, so we know to go update them.
207+
208+
You can run this via:
209+
210+
`./gradlew :instrumentation-docs:docSiteAudit`
211+
212+
This is setup to run nightly in a github action.
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.docs;
7+
8+
import static java.lang.System.exit;
9+
10+
import io.opentelemetry.instrumentation.docs.utils.FileManager;
11+
import java.io.IOException;
12+
import java.net.URI;
13+
import java.net.http.HttpClient;
14+
import java.net.http.HttpRequest;
15+
import java.net.http.HttpResponse;
16+
import java.util.ArrayList;
17+
import java.util.Arrays;
18+
import java.util.HashSet;
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.Objects;
22+
import java.util.Set;
23+
import java.util.TreeSet;
24+
import java.util.logging.Logger;
25+
import javax.annotation.Nullable;
26+
import org.yaml.snakeyaml.Yaml;
27+
28+
/**
29+
* This class is responsible for auditing and synchronizing documentation using the instrumentation
30+
* list yaml.
31+
*/
32+
public class DocSynchronization {
33+
private static final Logger logger = Logger.getLogger(DocSynchronization.class.getName());
34+
35+
private static final String DOCUMENTATION_DISABLE_LIST =
36+
"https://raw.githubusercontent.com/open-telemetry/opentelemetry.io/refs/heads/main/content/en/docs/zero-code/java/agent/disable.md";
37+
38+
// Used for consolidating instrumentation groups where we override the key with the value
39+
private static final Map<String, String> INSTRUMENTATION_DISABLE_OVERRIDES =
40+
Map.of("akka-actor-fork-join", "akka-actor");
41+
42+
private static final List<String> INSTRUMENTATION_EXCLUSIONS =
43+
List.of("resources", "spring-boot-resources");
44+
45+
private DocSynchronization() {}
46+
47+
/**
48+
* Retrieves contents of the disable page from the main branch of the documentation site.
49+
*
50+
* @return the file content as a string
51+
*/
52+
private static String getDocumentationDisableList(HttpClient client)
53+
throws IOException, InterruptedException {
54+
HttpRequest request =
55+
HttpRequest.newBuilder().uri(URI.create(DOCUMENTATION_DISABLE_LIST)).build();
56+
57+
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
58+
if (response.statusCode() >= 200 && response.statusCode() < 300) {
59+
return response.body();
60+
}
61+
throw new IOException("Failed to fetch instrumentation list: " + response);
62+
}
63+
64+
@SuppressWarnings("unchecked")
65+
public static List<String> parseInstrumentationList(String fileContent) {
66+
List<String> instrumentationList = new ArrayList<>();
67+
Yaml yaml = new Yaml();
68+
Map<String, Object> data = yaml.load(fileContent);
69+
70+
if (data != null && data.get("libraries") instanceof Map) {
71+
Map<String, List<Map<String, Object>>> libraries =
72+
(Map<String, List<Map<String, Object>>>) data.get("libraries");
73+
for (List<Map<String, Object>> libraryGroup : libraries.values()) {
74+
for (Map<String, Object> instrumentation : libraryGroup) {
75+
if (instrumentation.get("name") instanceof String) {
76+
instrumentationList.add((String) instrumentation.get("name"));
77+
}
78+
}
79+
}
80+
}
81+
return instrumentationList;
82+
}
83+
84+
/**
85+
* Identifies missing items in the instrumentation list that are not present in the documentation
86+
* disable list. Takes into account any overrides specified in INSTRUMENTATION_DISABLE_OVERRIDES
87+
* and excludes items listed in INSTRUMENTATION_EXCLUSIONS.
88+
*
89+
* @param documentationDisabledList a list of items that are documented
90+
* @param instrumentationList a list of instrumentations from the instrumentation list
91+
* @return a list of missing items that should be documented
92+
*/
93+
public static List<String> identifyMissingItems(
94+
List<String> documentationDisabledList, List<String> instrumentationList) {
95+
Set<String> documentationDisabledSet = new HashSet<>(documentationDisabledList);
96+
97+
Set<String> sanitizedInstrumentationItems = new TreeSet<>();
98+
for (String item : instrumentationList) {
99+
sanitizedInstrumentationItems.add(item.replaceFirst("-[0-9].*$", ""));
100+
}
101+
102+
List<String> missingItems = new ArrayList<>();
103+
for (String item : sanitizedInstrumentationItems) {
104+
if (INSTRUMENTATION_EXCLUSIONS.contains(item)) {
105+
continue; // Skip excluded items
106+
}
107+
String itemToCheck = INSTRUMENTATION_DISABLE_OVERRIDES.getOrDefault(item, item);
108+
boolean found = false;
109+
for (String disabledItem : documentationDisabledSet) {
110+
if (itemToCheck.startsWith(disabledItem)) {
111+
found = true;
112+
break;
113+
}
114+
}
115+
if (!found) {
116+
missingItems.add(item);
117+
}
118+
}
119+
return missingItems;
120+
}
121+
122+
/**
123+
* Retrieves the instrumentation list yaml file.
124+
*
125+
* @return a string representation of the instrumentation list
126+
*/
127+
@Nullable
128+
private static String getInstrumentationList() {
129+
// Identify path to repo so we can use absolute paths
130+
String baseRepoPath = System.getProperty("basePath");
131+
if (baseRepoPath == null) {
132+
baseRepoPath = "./";
133+
} else {
134+
baseRepoPath += "/";
135+
}
136+
137+
String file = baseRepoPath + "docs/instrumentation-list.yaml";
138+
return FileManager.readFileToString(file);
139+
}
140+
141+
/**
142+
* Parses the documentation disabled list from the file content and turns it into a list of
143+
* instrumentation names.
144+
*
145+
* @param fileContent the content of the disable.md documentation file
146+
* @return a list of instrumentation names that are documented
147+
*/
148+
public static List<String> parseDocumentationDisabledList(String fileContent) {
149+
List<String> instrumentationList = new ArrayList<>();
150+
String[] lines = fileContent.split("\\R");
151+
for (String line : lines) {
152+
if (line.trim().startsWith("|")) {
153+
String[] parts = line.split("\\|");
154+
if (parts.length > 2) {
155+
String potentialName = parts[2].trim();
156+
if (potentialName.startsWith("`") && potentialName.endsWith("`")) {
157+
String name = potentialName.substring(1, potentialName.length() - 1);
158+
instrumentationList.add(name);
159+
}
160+
}
161+
}
162+
}
163+
return instrumentationList;
164+
}
165+
166+
public static void main(String[] args) {
167+
HttpClient client = HttpClient.newHttpClient();
168+
169+
try {
170+
String content = getDocumentationDisableList(client);
171+
List<String> disabledList = parseDocumentationDisabledList(content);
172+
173+
String instrumentationListContent = Objects.requireNonNull(getInstrumentationList());
174+
List<String> instrumentationList = parseInstrumentationList(instrumentationListContent);
175+
176+
List<String> missingItems = identifyMissingItems(disabledList, instrumentationList);
177+
178+
if (missingItems.isEmpty()) {
179+
logger.info("No missing items found.");
180+
} else {
181+
StringBuilder sb = new StringBuilder();
182+
sb.append("Missing Instrumentation List (")
183+
.append(missingItems.size())
184+
.append(" item(s) missing):\n");
185+
missingItems.forEach(item -> sb.append(" - ").append(item).append("\n"));
186+
logger.severe(sb.toString());
187+
exit(1);
188+
}
189+
190+
} catch (IOException | InterruptedException e) {
191+
logger.severe("Error fetching instrumentation list: " + e.getMessage());
192+
logger.severe(Arrays.toString(e.getStackTrace()));
193+
exit(1);
194+
}
195+
}
196+
}

0 commit comments

Comments
 (0)