diff --git a/ecosystem-explorer/eslint.config.js b/ecosystem-explorer/eslint.config.js
index 00da6f2..61ceba5 100644
--- a/ecosystem-explorer/eslint.config.js
+++ b/ecosystem-explorer/eslint.config.js
@@ -36,5 +36,14 @@ export default defineConfig([
ecmaVersion: 2020,
globals: globals.browser,
},
+ rules: {
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ {
+ argsIgnorePattern: '^_',
+ varsIgnorePattern: '^_',
+ },
+ ],
+ },
},
])
diff --git a/ecosystem-explorer/public/data/sdk/sdk-options.json b/ecosystem-explorer/public/data/sdk/sdk-options.json
new file mode 100644
index 0000000..35de07c
--- /dev/null
+++ b/ecosystem-explorer/public/data/sdk/sdk-options.json
@@ -0,0 +1,171 @@
+{
+ "schema_version": "1.0",
+ "defaults": {
+ "file_format": "1.0",
+ "propagators": ["tracecontext", "baggage"],
+ "tracer_provider": {
+ "exporter_type": "otlp_http",
+ "exporters": {
+ "otlp_http": {
+ "endpoint": "http://localhost:4318",
+ "protocol": "http/protobuf"
+ },
+ "otlp_grpc": {
+ "endpoint": "http://localhost:4317"
+ },
+ "console": {}
+ },
+ "sampler": {
+ "type": "parent_based",
+ "root": "always_on",
+ "ratio": 1.0
+ },
+ "batch_processor": {
+ "schedule_delay": 5000,
+ "export_timeout": 30000,
+ "max_queue_size": 2048,
+ "max_export_batch_size": 512
+ }
+ }
+ },
+ "sections": {
+ "propagators": {
+ "name": "Propagators",
+ "description": "Configure context propagation formats for distributed tracing",
+ "options": [
+ {
+ "id": "tracecontext",
+ "name": "W3C TraceContext",
+ "description": "W3C Trace Context propagator. Required for interoperability with W3C-compatible systems."
+ },
+ {
+ "id": "baggage",
+ "name": "W3C Baggage",
+ "description": "W3C Baggage propagator for passing key-value pairs across service boundaries."
+ },
+ {
+ "id": "b3",
+ "name": "B3 (single header)",
+ "description": "Zipkin B3 single-header propagator for compatibility with Zipkin and older systems."
+ },
+ {
+ "id": "b3multi",
+ "name": "B3 Multi-header",
+ "description": "Zipkin B3 multi-header propagator."
+ }
+ ]
+ },
+ "tracer_provider": {
+ "name": "Traces",
+ "description": "Configure the OpenTelemetry tracer provider",
+ "exporter": {
+ "name": "Span Exporter",
+ "description": "Where to export trace data",
+ "options": [
+ {
+ "id": "otlp_http",
+ "name": "OTLP HTTP",
+ "description": "Export spans via OTLP over HTTP. Recommended for most setups.",
+ "settings": [
+ {
+ "name": "endpoint",
+ "label": "Endpoint URL",
+ "description": "OTLP HTTP endpoint",
+ "type": "string"
+ },
+ {
+ "name": "protocol",
+ "label": "Protocol",
+ "description": "OTLP transport protocol",
+ "type": "enum",
+ "options": ["http/protobuf", "http/json"]
+ }
+ ]
+ },
+ {
+ "id": "otlp_grpc",
+ "name": "OTLP gRPC",
+ "description": "Export spans via OTLP over gRPC.",
+ "settings": [
+ {
+ "name": "endpoint",
+ "label": "Endpoint URL",
+ "description": "OTLP gRPC endpoint",
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "id": "console",
+ "name": "Console",
+ "description": "Print spans to stdout. Useful for debugging.",
+ "settings": []
+ }
+ ]
+ },
+ "sampler": {
+ "name": "Sampler",
+ "description": "How to make sampling decisions for new traces",
+ "options": [
+ {
+ "id": "parent_based",
+ "name": "Parent Based",
+ "description": "Use the parent span's sampling decision. Root spans use the configured root sampler.",
+ "has_root": true,
+ "root_options": ["always_on", "always_off", "trace_id_ratio_based"]
+ },
+ {
+ "id": "always_on",
+ "name": "Always On",
+ "description": "Sample 100% of traces."
+ },
+ {
+ "id": "always_off",
+ "name": "Always Off",
+ "description": "Sample 0% of traces. Useful for disabling tracing."
+ },
+ {
+ "id": "trace_id_ratio_based",
+ "name": "Trace ID Ratio",
+ "description": "Sample a fixed percentage of traces based on trace ID.",
+ "has_ratio": true
+ }
+ ]
+ },
+ "batch_processor": {
+ "name": "Batch Processor",
+ "description": "Configure the batch span processor that buffers and exports spans",
+ "settings": [
+ {
+ "name": "schedule_delay",
+ "label": "Schedule Delay (ms)",
+ "description": "Delay between consecutive exports",
+ "type": "integer",
+ "min": 0
+ },
+ {
+ "name": "export_timeout",
+ "label": "Export Timeout (ms)",
+ "description": "Maximum time allowed to export data. 0 = no limit.",
+ "type": "integer",
+ "min": 0
+ },
+ {
+ "name": "max_queue_size",
+ "label": "Max Queue Size",
+ "description": "Maximum spans to queue before dropping",
+ "type": "integer",
+ "min": 1
+ },
+ {
+ "name": "max_export_batch_size",
+ "label": "Max Batch Size",
+ "description": "Maximum spans per export batch",
+ "type": "integer",
+ "min": 1
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/ecosystem-explorer/src/App.tsx b/ecosystem-explorer/src/App.tsx
index 83c3e45..3f8313d 100644
--- a/ecosystem-explorer/src/App.tsx
+++ b/ecosystem-explorer/src/App.tsx
@@ -21,7 +21,7 @@ import { JavaAgentPage } from "@/features/java-agent/java-agent-page";
import { CollectorPage } from "@/features/collector/collector-page";
import { NotFoundPage } from "@/features/not-found/not-found-page";
import { JavaInstrumentationListPage } from "@/features/java-agent/java-instrumentation-list-page";
-import { JavaConfigurationListPage } from "@/features/java-agent/java-configuration-list-page";
+import { JavaConfigurationBuilderPage } from "@/features/java-agent/configuration/java-configuration-builder-page";
import { InstrumentationDetailPage } from "@/features/java-agent/instrumentation-detail-page";
export default function App() {
@@ -38,7 +38,7 @@ export default function App() {
path="/java-agent/instrumentation/:version/:name"
element={ }
/>
- } />
+ } />
} />
} />
diff --git a/ecosystem-explorer/src/features/java-agent/configuration/components/export-toolbar.tsx b/ecosystem-explorer/src/features/java-agent/configuration/components/export-toolbar.tsx
new file mode 100644
index 0000000..3e40911
--- /dev/null
+++ b/ecosystem-explorer/src/features/java-agent/configuration/components/export-toolbar.tsx
@@ -0,0 +1,71 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { useState } from "react";
+import { useConfigurationBuilder } from "../hooks/use-configuration-builder";
+import { generateYamlFile } from "../utils/yaml-generator";
+
+export function ExportToolbar() {
+ const { state } = useConfigurationBuilder();
+ const [copyStatus, setCopyStatus] = useState<"idle" | "copied">("idle");
+
+ const output = generateYamlFile(state);
+ const fileExtension = ".yaml";
+
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(output);
+ setCopyStatus("copied");
+ setTimeout(() => setCopyStatus("idle"), 2000);
+ } catch (err) {
+ console.error("Failed to copy:", err);
+ }
+ };
+
+ const handleDownload = () => {
+ const blob = new Blob([output], { type: "text/plain" });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = `otel-config${fileExtension}`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ };
+
+ const disabled = state.selectedInstrumentations.size === 0;
+
+ return (
+
+
+ {copyStatus === "copied" ? "Copied!" : "Copy"}
+
+
+ Download
+
+
+ );
+}
diff --git a/ecosystem-explorer/src/features/java-agent/configuration/components/instrumentation-browser.tsx b/ecosystem-explorer/src/features/java-agent/configuration/components/instrumentation-browser.tsx
new file mode 100644
index 0000000..851629b
--- /dev/null
+++ b/ecosystem-explorer/src/features/java-agent/configuration/components/instrumentation-browser.tsx
@@ -0,0 +1,181 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { useMemo, useState } from "react";
+import { useInstrumentations } from "@/hooks/use-javaagent-data";
+import { useConfigurationBuilder } from "../hooks/use-configuration-builder";
+import { getInstrumentationDisplayName } from "../../utils/format";
+import type { InstrumentationData } from "@/types/javaagent";
+
+export function InstrumentationBrowser() {
+ const { state, dispatch } = useConfigurationBuilder();
+ const { data: instrumentations, loading } = useInstrumentations(state.version);
+ const [searchQuery, setSearchQuery] = useState("");
+
+ const filteredInstrumentations = useMemo(() => {
+ if (!instrumentations) return [];
+
+ return instrumentations.filter((instr) => {
+ if (searchQuery) {
+ const searchLower = searchQuery.toLowerCase();
+ const name = getInstrumentationDisplayName(instr).toLowerCase();
+ const description = (instr.description || "").toLowerCase();
+
+ return name.includes(searchLower) || description.includes(searchLower);
+ }
+ return true;
+ });
+ }, [instrumentations, searchQuery]);
+
+ const selectedInstrumentations = useMemo(() => {
+ return Array.from(state.selectedInstrumentations.values());
+ }, [state.selectedInstrumentations]);
+
+ const availableInstrumentations = useMemo(() => {
+ return filteredInstrumentations.filter(
+ (instr) => !state.selectedInstrumentations.has(instr.name)
+ );
+ }, [filteredInstrumentations, state.selectedInstrumentations]);
+
+ const handleAddAll = () => {
+ dispatch({
+ type: "ADD_ALL_INSTRUMENTATIONS",
+ instrumentations: availableInstrumentations,
+ });
+ };
+
+ const handleToggleInstrumentation = (instr: InstrumentationData) => {
+ if (state.selectedInstrumentations.has(instr.name)) {
+ dispatch({ type: "REMOVE_INSTRUMENTATION", name: instr.name });
+ } else {
+ dispatch({
+ type: "ADD_INSTRUMENTATION",
+ name: instr.name,
+ data: instr,
+ });
+ }
+ };
+
+ if (loading) {
+ return (
+
+
Loading instrumentations...
+
+ );
+ }
+
+ return (
+
+
+
+ Search instrumentations
+
+ setSearchQuery(e.target.value)}
+ className="w-full px-3 py-2 rounded-lg border border-border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary"
+ aria-label="Search instrumentations"
+ />
+
+
+ {selectedInstrumentations.length > 0 && (
+
+
+
+ Selected ({selectedInstrumentations.length})
+
+ dispatch({ type: "REMOVE_ALL_INSTRUMENTATIONS" })}
+ className="text-xs text-muted-foreground hover:text-destructive"
+ >
+ Remove All
+
+
+
+ {selectedInstrumentations.map((config) => (
+
+
+ {getInstrumentationDisplayName(config.data)}
+
+ handleToggleInstrumentation(config.data)}
+ className="text-muted-foreground hover:text-foreground"
+ aria-label={`Remove ${getInstrumentationDisplayName(config.data)}`}
+ >
+ ×
+
+
+ ))}
+
+
+ )}
+
+
+
+
+ Available ({availableInstrumentations.length})
+
+
+ Add All
+
+
+
+ {availableInstrumentations.length === 0 ? (
+
+ {searchQuery
+ ? "No instrumentations match your search"
+ : "All instrumentations selected"}
+
+ ) : (
+ availableInstrumentations.map((instr) => (
+
+ handleToggleInstrumentation(instr)}
+ className="mt-0.5 h-4 w-4 rounded border-border text-primary focus:ring-2 focus:ring-primary"
+ aria-label={`Select ${getInstrumentationDisplayName(instr)}`}
+ />
+
+
+ {getInstrumentationDisplayName(instr)}
+
+ {instr.description && (
+
+ {instr.description}
+
+ )}
+
+
+ ))
+ )}
+
+
+
+ );
+}
diff --git a/ecosystem-explorer/src/features/java-agent/configuration/components/output-preview.tsx b/ecosystem-explorer/src/features/java-agent/configuration/components/output-preview.tsx
new file mode 100644
index 0000000..7f3a607
--- /dev/null
+++ b/ecosystem-explorer/src/features/java-agent/configuration/components/output-preview.tsx
@@ -0,0 +1,40 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { useMemo } from "react";
+import { useConfigurationBuilder } from "../hooks/use-configuration-builder";
+import { generateYamlFile } from "../utils/yaml-generator";
+import { ExportToolbar } from "./export-toolbar";
+
+export function OutputPreview() {
+ const { state } = useConfigurationBuilder();
+
+ const output = useMemo(() => {
+ return generateYamlFile(state);
+ }, [state]);
+
+ return (
+
+ );
+}
diff --git a/ecosystem-explorer/src/features/java-agent/configuration/components/sdk-browser.tsx b/ecosystem-explorer/src/features/java-agent/configuration/components/sdk-browser.tsx
new file mode 100644
index 0000000..c4457f9
--- /dev/null
+++ b/ecosystem-explorer/src/features/java-agent/configuration/components/sdk-browser.tsx
@@ -0,0 +1,307 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { useEffect } from "react";
+import { useSdkOptions } from "@/hooks/use-sdk-options";
+import { useConfigurationBuilder } from "../hooks/use-configuration-builder";
+import type { SdkConfig } from "@/types/sdk";
+
+export function SdkBrowser() {
+ const { state, dispatch } = useConfigurationBuilder();
+ const { data: options, loading } = useSdkOptions();
+ const sdk = state.sdkConfig;
+
+ // Initialize state from sdk-options.json defaults on first load
+ useEffect(() => {
+ if (!options) return;
+ const d = options.defaults;
+ const sdkConfig: SdkConfig = {
+ fileFormat: d.file_format,
+ propagators: [...d.propagators],
+ tracerProvider: {
+ exporterType: d.tracer_provider.exporter_type,
+ exporterEndpoint:
+ d.tracer_provider.exporters[d.tracer_provider.exporter_type]?.endpoint ?? "",
+ exporterProtocol:
+ d.tracer_provider.exporters[d.tracer_provider.exporter_type]?.protocol ?? "",
+ samplerType: d.tracer_provider.sampler.type,
+ samplerRoot: d.tracer_provider.sampler.root,
+ samplerRatio: d.tracer_provider.sampler.ratio,
+ batchScheduleDelay: d.tracer_provider.batch_processor.schedule_delay,
+ batchExportTimeout: d.tracer_provider.batch_processor.export_timeout,
+ batchMaxQueueSize: d.tracer_provider.batch_processor.max_queue_size,
+ batchMaxExportBatchSize: d.tracer_provider.batch_processor.max_export_batch_size,
+ },
+ };
+ dispatch({ type: "LOAD_SDK_DEFAULTS", sdkConfig });
+ }, [options, dispatch]);
+
+ if (loading) {
+ return (
+
+
Loading SDK options...
+
+ );
+ }
+
+ if (!options) {
+ return Failed to load SDK options.
;
+ }
+
+ const { sections } = options;
+ const tracerSection = sections.tracer_provider;
+
+ const handleExporterTypeChange = (exporterType: string) => {
+ dispatch({ type: "SET_SDK_EXPORTER_TYPE", exporterType });
+ const exporterDefaults = options.defaults.tracer_provider.exporters[exporterType] ?? {};
+ dispatch({ type: "SET_SDK_EXPORTER_ENDPOINT", endpoint: exporterDefaults.endpoint ?? "" });
+ dispatch({ type: "SET_SDK_EXPORTER_PROTOCOL", protocol: exporterDefaults.protocol ?? "" });
+ };
+
+ return (
+
+ {/* Propagators */}
+
+
+
{sections.propagators.name}
+
{sections.propagators.description}
+
+
+ {sections.propagators.options.map((opt) => (
+
+ dispatch({ type: "TOGGLE_SDK_PROPAGATOR", propagatorId: opt.id })}
+ className="mt-0.5 h-4 w-4 rounded border-border text-primary focus:ring-2 focus:ring-primary"
+ />
+
+
+ {opt.name}
+
+
{opt.description}
+
+
+ ))}
+
+
+
+
+
+ {/* Exporter */}
+
+
+
{tracerSection.exporter.name}
+
+ {tracerSection.exporter.description}
+
+
+
+ {tracerSection.exporter.options.map((opt) => (
+
+ handleExporterTypeChange(opt.id)}
+ className="mt-0.5 h-4 w-4 border-border text-primary focus:ring-2 focus:ring-primary"
+ />
+
+
+ {opt.name}
+
+
{opt.description}
+
+
+ ))}
+
+
+ {/* Exporter endpoint field */}
+ {sdk.tracerProvider.exporterType !== "console" && (
+
+
+ Endpoint URL
+
+ dispatch({ type: "SET_SDK_EXPORTER_ENDPOINT", endpoint: e.target.value })
+ }
+ className="w-full px-3 py-1.5 rounded border border-border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary"
+ placeholder="http://localhost:4318"
+ />
+
+ {sdk.tracerProvider.exporterType === "otlp_http" && (
+
+ Protocol
+
+ dispatch({ type: "SET_SDK_EXPORTER_PROTOCOL", protocol: e.target.value })
+ }
+ className="w-full px-3 py-1.5 rounded border border-border bg-card text-sm focus:outline-none focus:ring-2 focus:ring-primary"
+ >
+ http/protobuf
+ http/json
+
+
+ )}
+
+ )}
+
+
+
+
+ {/* Sampler */}
+
+
+
{tracerSection.sampler.name}
+
+ {tracerSection.sampler.description}
+
+
+
+ {tracerSection.sampler.options.map((opt) => (
+
+ dispatch({ type: "SET_SDK_SAMPLER_TYPE", samplerType: opt.id })}
+ className="mt-0.5 h-4 w-4 border-border text-primary focus:ring-2 focus:ring-primary"
+ />
+
+
+ {opt.name}
+
+
{opt.description}
+
+
+ ))}
+
+
+ {/* Parent-based root sampler */}
+ {sdk.tracerProvider.samplerType === "parent_based" && (
+
+
Root sampler
+
dispatch({ type: "SET_SDK_SAMPLER_ROOT", root: e.target.value })}
+ className="w-full px-3 py-1.5 rounded border border-border bg-card text-sm focus:outline-none focus:ring-2 focus:ring-primary"
+ >
+ {tracerSection.sampler.options
+ .find((o) => o.id === "parent_based")
+ ?.root_options?.map((r) => (
+
+ {r.replace(/_/g, " ")}
+
+ ))}
+
+ {sdk.tracerProvider.samplerRoot === "trace_id_ratio_based" && (
+
+
+ Sampling ratio (0.0 – 1.0)
+
+
+ dispatch({ type: "SET_SDK_SAMPLER_RATIO", ratio: parseFloat(e.target.value) })
+ }
+ className="w-full px-3 py-1.5 rounded border border-border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary"
+ />
+
+ )}
+
+ )}
+
+ {/* Standalone ratio sampler */}
+ {sdk.tracerProvider.samplerType === "trace_id_ratio_based" && (
+
+
+ Sampling ratio (0.0 – 1.0)
+
+
+ dispatch({ type: "SET_SDK_SAMPLER_RATIO", ratio: parseFloat(e.target.value) })
+ }
+ className="w-full px-3 py-1.5 rounded border border-border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary"
+ />
+
+ )}
+
+
+
+
+ {/* Batch Processor */}
+
+
+
+ {tracerSection.batch_processor.name}
+
+
+ {tracerSection.batch_processor.description}
+
+
+
+ {tracerSection.batch_processor.settings.map((setting) => {
+ const stateKey = (
+ {
+ schedule_delay: "batchScheduleDelay",
+ export_timeout: "batchExportTimeout",
+ max_queue_size: "batchMaxQueueSize",
+ max_export_batch_size: "batchMaxExportBatchSize",
+ } as Record
+ )[setting.name];
+
+ if (!stateKey) return null;
+
+ return (
+
+
+ {setting.label}
+
+
+ dispatch({
+ type: "UPDATE_SDK_BATCH_SETTING",
+ key: stateKey,
+ value: parseInt(e.target.value, 10),
+ })
+ }
+ className="w-full px-3 py-1.5 rounded border border-border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary"
+ />
+
{setting.description}
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/ecosystem-explorer/src/features/java-agent/configuration/context/configuration-builder-context.ts b/ecosystem-explorer/src/features/java-agent/configuration/context/configuration-builder-context.ts
new file mode 100644
index 0000000..08ba053
--- /dev/null
+++ b/ecosystem-explorer/src/features/java-agent/configuration/context/configuration-builder-context.ts
@@ -0,0 +1,26 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { createContext, type Dispatch } from "react";
+import type { ConfigurationBuilderState, ConfigurationBuilderAction } from "@/types/javaagent";
+
+export interface ConfigurationBuilderContextValue {
+ state: ConfigurationBuilderState;
+ dispatch: Dispatch;
+}
+
+export const ConfigurationBuilderContext = createContext(
+ null
+);
diff --git a/ecosystem-explorer/src/features/java-agent/configuration/context/configuration-builder-context.tsx b/ecosystem-explorer/src/features/java-agent/configuration/context/configuration-builder-context.tsx
new file mode 100644
index 0000000..8b4de0d
--- /dev/null
+++ b/ecosystem-explorer/src/features/java-agent/configuration/context/configuration-builder-context.tsx
@@ -0,0 +1,282 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { useReducer, type ReactNode } from "react";
+import type { ConfigurationBuilderState, ConfigurationBuilderAction } from "@/types/javaagent";
+import { ConfigurationBuilderContext } from "./configuration-builder-context";
+
+const initialState: ConfigurationBuilderState = {
+ version: "",
+ activeArea: "instrumentation",
+ selectedInstrumentations: new Map(),
+ configOverrides: new Map(),
+ outputFormat: "properties",
+ isInitialized: false,
+ sdkConfig: {
+ fileFormat: "1.0",
+ propagators: ["tracecontext", "baggage"],
+ tracerProvider: {
+ exporterType: "otlp_http",
+ exporterEndpoint: "http://localhost:4318",
+ exporterProtocol: "http/protobuf",
+ samplerType: "parent_based",
+ samplerRoot: "always_on",
+ samplerRatio: 1.0,
+ batchScheduleDelay: 5000,
+ batchExportTimeout: 30000,
+ batchMaxQueueSize: 2048,
+ batchMaxExportBatchSize: 512,
+ },
+ },
+};
+
+function configurationBuilderReducer(
+ state: ConfigurationBuilderState,
+ action: ConfigurationBuilderAction
+): ConfigurationBuilderState {
+ switch (action.type) {
+ case "SET_VERSION":
+ return {
+ ...state,
+ version: action.version,
+ };
+
+ case "SET_ACTIVE_AREA":
+ return {
+ ...state,
+ activeArea: action.area,
+ };
+
+ case "ADD_INSTRUMENTATION": {
+ const newInstrumentations = new Map(state.selectedInstrumentations);
+ const enabledConfigs = new Set();
+
+ action.data.configurations?.forEach((config) => {
+ enabledConfigs.add(config.name);
+ });
+
+ newInstrumentations.set(action.name, {
+ name: action.name,
+ data: action.data,
+ enabledConfigs,
+ });
+
+ return {
+ ...state,
+ selectedInstrumentations: newInstrumentations,
+ };
+ }
+
+ case "ADD_ALL_INSTRUMENTATIONS": {
+ const newInstrumentations = new Map(state.selectedInstrumentations);
+ for (const instr of action.instrumentations) {
+ if (!newInstrumentations.has(instr.name)) {
+ const enabledConfigs = new Set();
+ instr.configurations?.forEach((config) => {
+ enabledConfigs.add(config.name);
+ });
+ newInstrumentations.set(instr.name, {
+ name: instr.name,
+ data: instr,
+ enabledConfigs,
+ });
+ }
+ }
+ return {
+ ...state,
+ selectedInstrumentations: newInstrumentations,
+ };
+ }
+
+ case "REMOVE_ALL_INSTRUMENTATIONS":
+ return {
+ ...state,
+ selectedInstrumentations: new Map(),
+ };
+
+ case "REMOVE_INSTRUMENTATION": {
+ const newInstrumentations = new Map(state.selectedInstrumentations);
+ newInstrumentations.delete(action.name);
+
+ return {
+ ...state,
+ selectedInstrumentations: newInstrumentations,
+ };
+ }
+
+ case "UPDATE_CONFIG": {
+ const newConfigOverrides = new Map(state.configOverrides);
+ const existingConfig = state.configOverrides.get(action.configName);
+
+ newConfigOverrides.set(action.configName, {
+ name: action.configName,
+ value: action.value,
+ isModified: true,
+ default: existingConfig?.default ?? action.value,
+ });
+
+ return {
+ ...state,
+ configOverrides: newConfigOverrides,
+ };
+ }
+
+ case "TOGGLE_CONFIG": {
+ const newInstrumentations = new Map(state.selectedInstrumentations);
+ const instrumentation = newInstrumentations.get(action.instrumentationName);
+
+ if (instrumentation) {
+ const newEnabledConfigs = new Set(instrumentation.enabledConfigs);
+ if (newEnabledConfigs.has(action.configName)) {
+ newEnabledConfigs.delete(action.configName);
+ } else {
+ newEnabledConfigs.add(action.configName);
+ }
+
+ newInstrumentations.set(action.instrumentationName, {
+ ...instrumentation,
+ enabledConfigs: newEnabledConfigs,
+ });
+ }
+
+ return {
+ ...state,
+ selectedInstrumentations: newInstrumentations,
+ };
+ }
+
+ case "SET_OUTPUT_FORMAT":
+ return {
+ ...state,
+ outputFormat: action.format,
+ };
+
+ case "LOAD_STATE": {
+ return {
+ ...state,
+ ...action.state,
+ };
+ }
+
+ case "LOAD_SDK_DEFAULTS":
+ return {
+ ...state,
+ sdkConfig: action.sdkConfig,
+ };
+
+ case "TOGGLE_SDK_PROPAGATOR": {
+ const current = state.sdkConfig.propagators;
+ const updated = current.includes(action.propagatorId)
+ ? current.filter((p) => p !== action.propagatorId)
+ : [...current, action.propagatorId];
+ return {
+ ...state,
+ sdkConfig: { ...state.sdkConfig, propagators: updated },
+ };
+ }
+
+ case "SET_SDK_EXPORTER_TYPE":
+ return {
+ ...state,
+ sdkConfig: {
+ ...state.sdkConfig,
+ tracerProvider: { ...state.sdkConfig.tracerProvider, exporterType: action.exporterType },
+ },
+ };
+
+ case "SET_SDK_EXPORTER_ENDPOINT":
+ return {
+ ...state,
+ sdkConfig: {
+ ...state.sdkConfig,
+ tracerProvider: { ...state.sdkConfig.tracerProvider, exporterEndpoint: action.endpoint },
+ },
+ };
+
+ case "SET_SDK_EXPORTER_PROTOCOL":
+ return {
+ ...state,
+ sdkConfig: {
+ ...state.sdkConfig,
+ tracerProvider: { ...state.sdkConfig.tracerProvider, exporterProtocol: action.protocol },
+ },
+ };
+
+ case "SET_SDK_SAMPLER_TYPE":
+ return {
+ ...state,
+ sdkConfig: {
+ ...state.sdkConfig,
+ tracerProvider: { ...state.sdkConfig.tracerProvider, samplerType: action.samplerType },
+ },
+ };
+
+ case "SET_SDK_SAMPLER_ROOT":
+ return {
+ ...state,
+ sdkConfig: {
+ ...state.sdkConfig,
+ tracerProvider: { ...state.sdkConfig.tracerProvider, samplerRoot: action.root },
+ },
+ };
+
+ case "SET_SDK_SAMPLER_RATIO":
+ return {
+ ...state,
+ sdkConfig: {
+ ...state.sdkConfig,
+ tracerProvider: { ...state.sdkConfig.tracerProvider, samplerRatio: action.ratio },
+ },
+ };
+
+ case "UPDATE_SDK_BATCH_SETTING":
+ return {
+ ...state,
+ sdkConfig: {
+ ...state.sdkConfig,
+ tracerProvider: { ...state.sdkConfig.tracerProvider, [action.key]: action.value },
+ },
+ };
+
+ case "RESET":
+ return {
+ ...initialState,
+ version: state.version,
+ };
+
+ case "MARK_INITIALIZED":
+ return {
+ ...state,
+ isInitialized: true,
+ };
+
+ default:
+ return state;
+ }
+}
+
+interface ConfigurationBuilderProviderProps {
+ children: ReactNode;
+}
+
+export function ConfigurationBuilderProvider({ children }: ConfigurationBuilderProviderProps) {
+ const [state, dispatch] = useReducer(configurationBuilderReducer, initialState);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/ecosystem-explorer/src/features/java-agent/configuration/hooks/use-configuration-builder.ts b/ecosystem-explorer/src/features/java-agent/configuration/hooks/use-configuration-builder.ts
new file mode 100644
index 0000000..bbd1ba6
--- /dev/null
+++ b/ecosystem-explorer/src/features/java-agent/configuration/hooks/use-configuration-builder.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { useContext } from "react";
+import { ConfigurationBuilderContext } from "../context/configuration-builder-context";
+
+export function useConfigurationBuilder() {
+ const context = useContext(ConfigurationBuilderContext);
+
+ if (!context) {
+ throw new Error("useConfigurationBuilder must be used within a ConfigurationBuilderProvider");
+ }
+
+ return context;
+}
diff --git a/ecosystem-explorer/src/features/java-agent/configuration/java-configuration-builder-page.tsx b/ecosystem-explorer/src/features/java-agent/configuration/java-configuration-builder-page.tsx
new file mode 100644
index 0000000..6d9470d
--- /dev/null
+++ b/ecosystem-explorer/src/features/java-agent/configuration/java-configuration-builder-page.tsx
@@ -0,0 +1,147 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { useEffect } from "react";
+import { BackButton } from "@/components/ui/back-button";
+import { useVersions } from "@/hooks/use-javaagent-data";
+import { ConfigurationBuilderProvider } from "./context/configuration-builder-context.tsx";
+import { useConfigurationBuilder } from "./hooks/use-configuration-builder";
+import { InstrumentationBrowser } from "./components/instrumentation-browser";
+import { SdkBrowser } from "./components/sdk-browser";
+import { OutputPreview } from "./components/output-preview";
+
+function ConfigurationBuilderContent() {
+ const { data: versionsData, loading: versionsLoading } = useVersions();
+ const { state, dispatch } = useConfigurationBuilder();
+
+ const latestVersion = versionsData?.versions.find((v) => v.is_latest)?.version ?? "";
+
+ useEffect(() => {
+ if (latestVersion && !state.version) {
+ dispatch({ type: "SET_VERSION", version: latestVersion });
+ }
+ }, [latestVersion, state.version, dispatch]);
+
+ if (versionsLoading) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+ Version:
+
+ dispatch({ type: "SET_VERSION", version: e.target.value })}
+ className="px-3 py-2 rounded-lg border border-border bg-card text-sm"
+ aria-label="Select Java Agent version"
+ >
+ {versionsData?.versions.map((version) => (
+
+ {version.version}
+ {version.is_latest ? " (latest)" : ""}
+
+ ))}
+
+
+
+
+
+
Configuration Builder
+
+ Build and customize your OpenTelemetry Java Agent configuration
+
+
+
+
+ dispatch({ type: "SET_ACTIVE_AREA", area: "instrumentation" })}
+ className={`px-4 py-2 font-medium border-b-2 transition-colors ${
+ state.activeArea === "instrumentation"
+ ? "border-primary text-primary"
+ : "border-transparent text-muted-foreground hover:text-foreground"
+ }`}
+ >
+ Instrumentation
+
+ dispatch({ type: "SET_ACTIVE_AREA", area: "sdk" })}
+ className={`px-4 py-2 font-medium border-b-2 transition-colors ${
+ state.activeArea === "sdk"
+ ? "border-primary text-primary"
+ : "border-transparent text-muted-foreground hover:text-foreground"
+ }`}
+ >
+ SDK
+
+
+
+
+
+ {state.activeArea === "instrumentation" && (
+
+
Instrumentation Browser
+
+
+ )}
+ {state.activeArea === "sdk" && (
+
+
SDK Configuration
+
+
+ )}
+
+
+
+
Output Preview
+
+
+
+
+
+ );
+}
+
+export function JavaConfigurationBuilderPage() {
+ return (
+
+
+
+ );
+}
diff --git a/ecosystem-explorer/src/features/java-agent/configuration/utils/common-config-detector.ts b/ecosystem-explorer/src/features/java-agent/configuration/utils/common-config-detector.ts
new file mode 100644
index 0000000..61efb5e
--- /dev/null
+++ b/ecosystem-explorer/src/features/java-agent/configuration/utils/common-config-detector.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import type { InstrumentationConfig, CommonConfig, Configuration } from "@/types/javaagent";
+
+export function detectCommonConfigs(
+ instrumentations: Map
+): CommonConfig[] {
+ const usage = new Map();
+
+ for (const [name, instr] of instrumentations) {
+ if (!instr.data.configurations) continue;
+
+ for (const config of instr.data.configurations) {
+ if (!instr.enabledConfigs.has(config.name)) continue;
+
+ const existing = usage.get(config.name);
+ if (existing) {
+ existing.usedBy.push(name);
+ } else {
+ usage.set(config.name, { config, usedBy: [name] });
+ }
+ }
+ }
+
+ return Array.from(usage.entries())
+ .filter(([_, info]) => info.usedBy.length >= 2)
+ .map(([name, info]) => ({
+ name,
+ config: info.config,
+ usedBy: info.usedBy,
+ }))
+ .sort((a, b) => b.usedBy.length - a.usedBy.length);
+}
diff --git a/ecosystem-explorer/src/features/java-agent/configuration/utils/config-name-converter.test.ts b/ecosystem-explorer/src/features/java-agent/configuration/utils/config-name-converter.test.ts
new file mode 100644
index 0000000..2f5da56
--- /dev/null
+++ b/ecosystem-explorer/src/features/java-agent/configuration/utils/config-name-converter.test.ts
@@ -0,0 +1,264 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { describe, it, expect } from "vitest";
+import { flatToDeclarative, declarativeToFlat, flatToShellVar } from "./config-name-converter";
+
+describe("config-name-converter", () => {
+ describe("flatToDeclarative", () => {
+ describe("SPECIAL_MAPPINGS", () => {
+ it("converts general HTTP client request headers", () => {
+ expect(flatToDeclarative("otel.instrumentation.http.client.capture-request-headers")).toBe(
+ "general.http.client.request_captured_headers"
+ );
+ });
+
+ it("converts general HTTP client response headers", () => {
+ expect(flatToDeclarative("otel.instrumentation.http.client.capture-response-headers")).toBe(
+ "general.http.client.response_captured_headers"
+ );
+ });
+
+ it("converts general HTTP server request headers", () => {
+ expect(flatToDeclarative("otel.instrumentation.http.server.capture-request-headers")).toBe(
+ "general.http.server.request_captured_headers"
+ );
+ });
+
+ it("converts general HTTP server response headers", () => {
+ expect(flatToDeclarative("otel.instrumentation.http.server.capture-response-headers")).toBe(
+ "general.http.server.response_captured_headers"
+ );
+ });
+
+ it("converts HTTP known methods", () => {
+ expect(flatToDeclarative("otel.instrumentation.http.known-methods")).toBe(
+ "instrumentation.java.common.http.known_methods"
+ );
+ });
+
+ it("converts HTTP client redact query parameters with /development", () => {
+ expect(
+ flatToDeclarative("otel.instrumentation.http.client.experimental.redact-query-parameters")
+ ).toBe("instrumentation.java.common.http.client.redact_query_parameters/development");
+ });
+
+ it("converts HTTP client emit experimental telemetry with /development", () => {
+ expect(
+ flatToDeclarative("otel.instrumentation.http.client.emit-experimental-telemetry")
+ ).toBe("instrumentation.java.common.http.client.emit_experimental_telemetry/development");
+ });
+
+ it("converts HTTP server emit experimental telemetry with /development", () => {
+ expect(
+ flatToDeclarative("otel.instrumentation.http.server.emit-experimental-telemetry")
+ ).toBe("instrumentation.java.common.http.server.emit_experimental_telemetry/development");
+ });
+
+ it("converts common db statement sanitizer", () => {
+ expect(
+ flatToDeclarative("otel.instrumentation.common.db-statement-sanitizer.enabled")
+ ).toBe("instrumentation.java.common.database.statement_sanitizer.enabled");
+ });
+
+ it("converts common db sqlcommenter with /development", () => {
+ expect(
+ flatToDeclarative("otel.instrumentation.common.experimental.db-sqlcommenter.enabled")
+ ).toBe("instrumentation.java.common.database.sqlcommenter/development.enabled");
+ });
+
+ it("converts messaging receive telemetry with /development", () => {
+ expect(
+ flatToDeclarative("otel.instrumentation.messaging.experimental.receive-telemetry.enabled")
+ ).toBe("instrumentation.java.common.messaging.receive_telemetry/development.enabled");
+ });
+
+ it("converts messaging capture headers with /development", () => {
+ expect(
+ flatToDeclarative("otel.instrumentation.messaging.experimental.capture-headers")
+ ).toBe("instrumentation.java.common.messaging.capture_headers/development");
+ });
+
+ it("converts genai capture message content", () => {
+ expect(flatToDeclarative("otel.instrumentation.genai.capture-message-content")).toBe(
+ "instrumentation.java.common.gen_ai.capture_message_content"
+ );
+ });
+
+ it("converts span suppression strategy with /development", () => {
+ expect(
+ flatToDeclarative("otel.instrumentation.experimental.span-suppression-strategy")
+ ).toBe("instrumentation.java.common.span_suppression_strategy/development");
+ });
+
+ it("converts opentelemetry annotations exclude methods", () => {
+ expect(
+ flatToDeclarative("otel.instrumentation.opentelemetry-annotations.exclude-methods")
+ ).toBe("instrumentation.java.opentelemetry_extension_annotations.exclude_methods");
+ });
+
+ it("converts semconv stability opt-in", () => {
+ expect(flatToDeclarative("otel.semconv-stability.opt-in")).toBe(
+ "general.semconv_stability.opt_in"
+ );
+ });
+ });
+
+ describe("mechanical conversion", () => {
+ it("converts basic instrumentation property", () => {
+ expect(flatToDeclarative("otel.instrumentation.jdbc.statement-sanitizer.enabled")).toBe(
+ "instrumentation.java.jdbc.statement_sanitizer.enabled"
+ );
+ });
+
+ it("replaces hyphens with underscores", () => {
+ expect(flatToDeclarative("otel.instrumentation.my-custom-lib.some-property")).toBe(
+ "instrumentation.java.my_custom_lib.some_property"
+ );
+ });
+
+ it("handles experimental prefix with /development suffix", () => {
+ expect(
+ flatToDeclarative("otel.instrumentation.jdbc.experimental.transaction.enabled")
+ ).toBe("instrumentation.java.jdbc.experimental/development.transaction.enabled");
+ });
+
+ it("handles experimental- prefix in segment", () => {
+ expect(
+ flatToDeclarative("otel.instrumentation.spring-webmvc.experimental-span-attributes")
+ ).toBe("instrumentation.java.spring_webmvc.span_attributes/development");
+ });
+
+ it("preserves non-instrumentation properties", () => {
+ expect(flatToDeclarative("otel.some.other.property")).toBe("otel.some.other.property");
+ });
+
+ it("handles properties without otel.instrumentation prefix", () => {
+ expect(flatToDeclarative("custom.property.name")).toBe("custom.property.name");
+ });
+ });
+ });
+
+ describe("declarativeToFlat", () => {
+ describe("reverse SPECIAL_MAPPINGS", () => {
+ it("converts general HTTP client request headers back", () => {
+ expect(declarativeToFlat("general.http.client.request_captured_headers")).toBe(
+ "otel.instrumentation.http.client.capture-request-headers"
+ );
+ });
+
+ it("converts HTTP known methods back", () => {
+ expect(declarativeToFlat("instrumentation.java.common.http.known_methods")).toBe(
+ "otel.instrumentation.http.known-methods"
+ );
+ });
+
+ it("converts HTTP client emit experimental telemetry back", () => {
+ expect(
+ declarativeToFlat(
+ "instrumentation.java.common.http.client.emit_experimental_telemetry/development"
+ )
+ ).toBe("otel.instrumentation.http.client.emit-experimental-telemetry");
+ });
+
+ it("converts common db statement sanitizer back", () => {
+ expect(
+ declarativeToFlat("instrumentation.java.common.database.statement_sanitizer.enabled")
+ ).toBe("otel.instrumentation.common.db-statement-sanitizer.enabled");
+ });
+
+ it("converts semconv stability opt-in back", () => {
+ expect(declarativeToFlat("general.semconv_stability.opt_in")).toBe(
+ "otel.semconv-stability.opt-in"
+ );
+ });
+ });
+
+ describe("mechanical reverse conversion", () => {
+ it("converts basic instrumentation property back", () => {
+ expect(declarativeToFlat("instrumentation.java.jdbc.statement_sanitizer.enabled")).toBe(
+ "otel.instrumentation.jdbc.statement-sanitizer.enabled"
+ );
+ });
+
+ it("replaces underscores with hyphens", () => {
+ expect(declarativeToFlat("instrumentation.java.my_custom_lib.some_property")).toBe(
+ "otel.instrumentation.my-custom-lib.some-property"
+ );
+ });
+
+ it("strips /development suffix", () => {
+ expect(
+ declarativeToFlat(
+ "instrumentation.java.jdbc.experimental/development.transaction.enabled"
+ )
+ ).toBe("otel.instrumentation.jdbc.experimental.transaction.enabled");
+ });
+
+ it("preserves non-instrumentation properties", () => {
+ expect(declarativeToFlat("custom.property.name")).toBe("custom.property.name");
+ });
+ });
+ });
+
+ describe("flatToShellVar", () => {
+ it("converts to uppercase with underscores", () => {
+ expect(flatToShellVar("otel.instrumentation.jdbc.statement-sanitizer.enabled")).toBe(
+ "OTEL_INSTRUMENTATION_JDBC_STATEMENT_SANITIZER_ENABLED"
+ );
+ });
+
+ it("replaces dots with underscores", () => {
+ expect(flatToShellVar("otel.instrumentation.http.known-methods")).toBe(
+ "OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS"
+ );
+ });
+
+ it("replaces hyphens with underscores", () => {
+ expect(flatToShellVar("otel.instrumentation.my-custom-lib.some-property")).toBe(
+ "OTEL_INSTRUMENTATION_MY_CUSTOM_LIB_SOME_PROPERTY"
+ );
+ });
+
+ it("handles experimental properties", () => {
+ expect(flatToShellVar("otel.instrumentation.jdbc.experimental.transaction.enabled")).toBe(
+ "OTEL_INSTRUMENTATION_JDBC_EXPERIMENTAL_TRANSACTION_ENABLED"
+ );
+ });
+ });
+
+ describe("round-trip conversions", () => {
+ it("flat -> declarative -> flat for standard property", () => {
+ const original = "otel.instrumentation.jdbc.statement-sanitizer.enabled";
+ const declarative = flatToDeclarative(original);
+ const backToFlat = declarativeToFlat(declarative);
+ expect(backToFlat).toBe(original);
+ });
+
+ it("flat -> declarative -> flat for experimental property", () => {
+ const original = "otel.instrumentation.jdbc.experimental.transaction.enabled";
+ const declarative = flatToDeclarative(original);
+ const backToFlat = declarativeToFlat(declarative);
+ expect(backToFlat).toBe(original);
+ });
+
+ it("flat -> declarative -> flat for SPECIAL_MAPPINGS", () => {
+ const original = "otel.instrumentation.http.known-methods";
+ const declarative = flatToDeclarative(original);
+ const backToFlat = declarativeToFlat(declarative);
+ expect(backToFlat).toBe(original);
+ });
+ });
+});
diff --git a/ecosystem-explorer/src/features/java-agent/configuration/utils/config-name-converter.ts b/ecosystem-explorer/src/features/java-agent/configuration/utils/config-name-converter.ts
new file mode 100644
index 0000000..c25e78e
--- /dev/null
+++ b/ecosystem-explorer/src/features/java-agent/configuration/utils/config-name-converter.ts
@@ -0,0 +1,108 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const SPECIAL_MAPPINGS_REVERSE: Record = {
+ "otel.instrumentation.http.client.capture-request-headers":
+ "general.http.client.request_captured_headers",
+ "otel.instrumentation.http.client.capture-response-headers":
+ "general.http.client.response_captured_headers",
+ "otel.instrumentation.http.server.capture-request-headers":
+ "general.http.server.request_captured_headers",
+ "otel.instrumentation.http.server.capture-response-headers":
+ "general.http.server.response_captured_headers",
+ "otel.instrumentation.http.known-methods": "java.common.http.known_methods",
+ "otel.instrumentation.http.client.experimental.redact-query-parameters":
+ "java.common.http.client.redact_query_parameters/development",
+ "otel.instrumentation.http.client.emit-experimental-telemetry":
+ "java.common.http.client.emit_experimental_telemetry/development",
+ "otel.instrumentation.http.server.emit-experimental-telemetry":
+ "java.common.http.server.emit_experimental_telemetry/development",
+ "otel.instrumentation.common.db-statement-sanitizer.enabled":
+ "java.common.database.statement_sanitizer.enabled",
+ "otel.instrumentation.common.experimental.db-sqlcommenter.enabled":
+ "java.common.database.sqlcommenter/development.enabled",
+ "otel.instrumentation.messaging.experimental.receive-telemetry.enabled":
+ "java.common.messaging.receive_telemetry/development.enabled",
+ "otel.instrumentation.messaging.experimental.capture-headers":
+ "java.common.messaging.capture_headers/development",
+ "otel.instrumentation.genai.capture-message-content":
+ "java.common.gen_ai.capture_message_content",
+ "otel.instrumentation.experimental.span-suppression-strategy":
+ "java.common.span_suppression_strategy/development",
+ "otel.instrumentation.opentelemetry-annotations.exclude-methods":
+ "java.opentelemetry_extension_annotations.exclude_methods",
+ "otel.semconv-stability.opt-in": "general.semconv_stability.opt_in",
+};
+
+export function flatToDeclarative(flatProp: string): string {
+ const specialMapping = SPECIAL_MAPPINGS_REVERSE[flatProp];
+ if (specialMapping) {
+ if (specialMapping.startsWith("java.")) {
+ return `instrumentation.${specialMapping}`;
+ }
+ return specialMapping;
+ }
+
+ if (!flatProp.startsWith("otel.instrumentation.")) {
+ return flatProp;
+ }
+
+ let path = flatProp.substring("otel.instrumentation.".length);
+
+ const segments = path.split(".");
+ const convertedSegments = segments.map((segment) => {
+ if (segment.startsWith("experimental-")) {
+ const withoutExp = segment.replace(/^experimental-/, "");
+ return withoutExp ? `${withoutExp}/development` : "experimental/development";
+ } else if (segment === "experimental") {
+ return "experimental/development";
+ }
+ return segment;
+ });
+
+ path = convertedSegments.join(".").replace(/-/g, "_");
+
+ return `instrumentation.java.${path}`;
+}
+
+export function declarativeToFlat(declarativePath: string): string {
+ const reverseMapping = Object.entries(SPECIAL_MAPPINGS_REVERSE).find(
+ ([_, declarative]) =>
+ declarative === declarativePath || `instrumentation.${declarative}` === declarativePath
+ );
+ if (reverseMapping) {
+ return reverseMapping[0];
+ }
+
+ if (declarativePath.startsWith("instrumentation.java.")) {
+ let path = declarativePath.substring("instrumentation.java.".length);
+
+ path = path.replace(/\/development/g, "");
+
+ const segments = path.split(".");
+ const convertedSegments = segments.map((segment) => {
+ return segment.replace(/_/g, "-");
+ });
+
+ return `otel.instrumentation.${convertedSegments.join(".")}`;
+ }
+
+ return declarativePath;
+}
+
+export function flatToShellVar(flatProp: string): string {
+ return flatProp.replace(/\./g, "_").replace(/-/g, "_").toUpperCase();
+}
diff --git a/ecosystem-explorer/src/features/java-agent/configuration/utils/sdk-yaml-generator.ts b/ecosystem-explorer/src/features/java-agent/configuration/utils/sdk-yaml-generator.ts
new file mode 100644
index 0000000..0552a7a
--- /dev/null
+++ b/ecosystem-explorer/src/features/java-agent/configuration/utils/sdk-yaml-generator.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import type { SdkConfig } from "@/types/sdk";
+
+export function generateSdkYaml(sdk: SdkConfig): string {
+ const lines: string[] = [];
+
+ lines.push(`file_format: "${sdk.fileFormat}"`);
+
+ // tracer_provider
+ lines.push("tracer_provider:");
+ lines.push(" processors:");
+ lines.push(" - batch:");
+ lines.push(" exporter:");
+
+ const { exporterType, exporterEndpoint, exporterProtocol } = sdk.tracerProvider;
+
+ if (exporterType === "console") {
+ lines.push(" console:");
+ } else {
+ lines.push(` ${exporterType}:`);
+ if (exporterEndpoint) {
+ lines.push(` endpoint: ${exporterEndpoint}`);
+ }
+ if (exporterType === "otlp_http" && exporterProtocol) {
+ lines.push(` protocol: ${exporterProtocol}`);
+ }
+ }
+
+ // sampler
+ const { samplerType, samplerRoot, samplerRatio } = sdk.tracerProvider;
+ lines.push(" sampler:");
+
+ if (samplerType === "parent_based") {
+ lines.push(" parent_based:");
+ lines.push(" root:");
+ if (samplerRoot === "trace_id_ratio_based") {
+ lines.push(" trace_id_ratio_based:");
+ lines.push(` ratio: ${samplerRatio}`);
+ } else {
+ lines.push(` ${samplerRoot}:`);
+ }
+ } else if (samplerType === "trace_id_ratio_based") {
+ lines.push(" trace_id_ratio_based:");
+ lines.push(` ratio: ${samplerRatio}`);
+ } else {
+ lines.push(` ${samplerType}:`);
+ }
+
+ // propagator
+ if (sdk.propagators.length > 0) {
+ lines.push("propagator:");
+ lines.push(" composite:");
+ for (const propagator of sdk.propagators) {
+ lines.push(` - ${propagator}:`);
+ }
+ }
+
+ return lines.join("\n");
+}
diff --git a/ecosystem-explorer/src/features/java-agent/configuration/utils/yaml-generator.ts b/ecosystem-explorer/src/features/java-agent/configuration/utils/yaml-generator.ts
new file mode 100644
index 0000000..bac7be1
--- /dev/null
+++ b/ecosystem-explorer/src/features/java-agent/configuration/utils/yaml-generator.ts
@@ -0,0 +1,131 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import type { ConfigurationBuilderState } from "@/types/javaagent";
+import { flatToDeclarative } from "./config-name-converter";
+import { generateSdkYaml } from "./sdk-yaml-generator";
+
+type NestedConfig = Record;
+
+function setNestedValue(obj: NestedConfig, path: string[], value: unknown): void {
+ let current = obj;
+
+ for (let i = 0; i < path.length - 1; i++) {
+ const key = path[i];
+ if (!(key in current)) {
+ current[key] = {};
+ }
+ current = current[key] as NestedConfig;
+ }
+
+ const lastKey = path[path.length - 1];
+ current[lastKey] = value;
+}
+
+function formatYamlValue(value: unknown): string {
+ if (typeof value === "boolean") {
+ return String(value);
+ }
+ if (typeof value === "number") {
+ return String(value);
+ }
+ if (typeof value === "string") {
+ if (value === "") {
+ return '""';
+ }
+ if (value.includes(",") || value.includes(":") || value.includes("#")) {
+ return `"${value}"`;
+ }
+ return value;
+ }
+ if (Array.isArray(value)) {
+ if (value.length === 0) {
+ return "[]";
+ }
+ return `[${value.map(formatYamlValue).join(", ")}]`;
+ }
+ return String(value);
+}
+
+function serializeYaml(obj: NestedConfig, indent = 0): string {
+ const lines: string[] = [];
+ const indentStr = " ".repeat(indent);
+
+ const sortedKeys = Object.keys(obj).sort((a, b) => a.localeCompare(b));
+
+ for (const key of sortedKeys) {
+ const value = obj[key];
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
+ if (Object.keys(value as NestedConfig).length === 0) continue;
+ lines.push(`${indentStr}${key}:`);
+ lines.push(serializeYaml(value as NestedConfig, indent + 1));
+ } else {
+ lines.push(`${indentStr}${key}: ${formatYamlValue(value)}`);
+ }
+ }
+
+ return lines.join("\n");
+}
+
+export function generateYamlFile(state: ConfigurationBuilderState): string {
+ const lines: string[] = [];
+
+ lines.push("# OpenTelemetry Java Agent Configuration");
+ lines.push("# Generated by Ecosystem Explorer");
+ lines.push(`# Version: ${state.version}`);
+ lines.push("");
+
+ const instrumentations = Array.from(state.selectedInstrumentations.values());
+
+ // Build instrumentation config block
+ const seenKeys = new Set();
+ const rootConfig: NestedConfig = {};
+
+ for (const instrumentation of instrumentations) {
+ const configs =
+ instrumentation.data.configurations?.filter((config) =>
+ instrumentation.enabledConfigs.has(config.name)
+ ) || [];
+
+ for (const config of configs) {
+ const declarativeName = flatToDeclarative(config.name);
+ if (!seenKeys.has(declarativeName)) {
+ seenKeys.add(declarativeName);
+ const pathSegments = declarativeName.split(".");
+ setNestedValue(rootConfig, pathSegments, config.default);
+ }
+ }
+ }
+
+ const hasInstrumentation = Object.keys(rootConfig).length > 0;
+ const hasSdk = true; // SDK config is always included
+
+ if (hasSdk) {
+ lines.push("# --- SDK Configuration ---");
+ lines.push(generateSdkYaml(state.sdkConfig));
+ }
+
+ if (hasInstrumentation) {
+ if (hasSdk) lines.push("");
+ lines.push("# --- Instrumentation Configuration ---");
+ lines.push(serializeYaml(rootConfig));
+ }
+
+ if (!hasSdk && !hasInstrumentation) {
+ lines.push("# No configurations selected");
+ }
+
+ return lines.join("\n");
+}
diff --git a/ecosystem-explorer/src/hooks/use-sdk-options.ts b/ecosystem-explorer/src/hooks/use-sdk-options.ts
new file mode 100644
index 0000000..ca1e2bb
--- /dev/null
+++ b/ecosystem-explorer/src/hooks/use-sdk-options.ts
@@ -0,0 +1,62 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { useState, useEffect } from "react";
+import type { SdkOptions } from "@/types/sdk";
+
+interface DataState {
+ data: T | null;
+ loading: boolean;
+ error: Error | null;
+}
+
+export function useSdkOptions(): DataState {
+ const [state, setState] = useState>({
+ data: null,
+ loading: true,
+ error: null,
+ });
+
+ useEffect(() => {
+ let cancelled = false;
+
+ async function loadData() {
+ try {
+ const res = await fetch("/data/sdk/sdk-options.json");
+ if (!res.ok) throw new Error(`Failed to load sdk-options.json: ${res.status}`);
+ const data: SdkOptions = await res.json();
+ if (!cancelled) {
+ setState({ data, loading: false, error: null });
+ }
+ } catch (error) {
+ if (!cancelled) {
+ setState({
+ data: null,
+ loading: false,
+ error: error instanceof Error ? error : new Error(String(error)),
+ });
+ }
+ }
+ }
+
+ loadData();
+
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ return state;
+}
diff --git a/ecosystem-explorer/src/types/javaagent.ts b/ecosystem-explorer/src/types/javaagent.ts
index fcc27e5..2d249e6 100644
--- a/ecosystem-explorer/src/types/javaagent.ts
+++ b/ecosystem-explorer/src/types/javaagent.ts
@@ -87,3 +87,87 @@ export interface Attribute {
| "DOUBLE_ARRAY"
| "BOOLEAN_ARRAY";
}
+
+export interface ConfigurationBuilderState {
+ version: string;
+ activeArea: "instrumentation" | "sdk";
+ selectedInstrumentations: Map;
+ configOverrides: Map;
+ outputFormat: "properties" | "env";
+ isInitialized: boolean;
+ sdkConfig: import("@/types/sdk").SdkConfig;
+}
+
+export interface InstrumentationConfig {
+ name: string;
+ data: InstrumentationData;
+ enabledConfigs: Set;
+}
+
+export interface ConfigValue {
+ name: string;
+ value: string | boolean | number;
+ isModified: boolean;
+ default: string | boolean | number;
+}
+
+export interface CommonConfig {
+ name: string;
+ config: Configuration;
+ usedBy: string[];
+}
+
+export type ConfigurationBuilderAction =
+ | { type: "SET_VERSION"; version: string }
+ | { type: "SET_ACTIVE_AREA"; area: "instrumentation" | "sdk" }
+ | {
+ type: "ADD_INSTRUMENTATION";
+ name: string;
+ data: InstrumentationData;
+ }
+ | { type: "REMOVE_INSTRUMENTATION"; name: string }
+ | {
+ type: "UPDATE_CONFIG";
+ configName: string;
+ value: string | boolean | number;
+ }
+ | { type: "TOGGLE_CONFIG"; instrumentationName: string; configName: string }
+ | { type: "SET_OUTPUT_FORMAT"; format: "properties" | "env" }
+ | {
+ type: "LOAD_STATE";
+ state: Partial;
+ }
+ | { type: "ADD_ALL_INSTRUMENTATIONS"; instrumentations: InstrumentationData[] }
+ | { type: "REMOVE_ALL_INSTRUMENTATIONS" }
+ | { type: "LOAD_SDK_DEFAULTS"; sdkConfig: import("@/types/sdk").SdkConfig }
+ | { type: "TOGGLE_SDK_PROPAGATOR"; propagatorId: string }
+ | { type: "SET_SDK_EXPORTER_TYPE"; exporterType: string }
+ | { type: "SET_SDK_EXPORTER_ENDPOINT"; endpoint: string }
+ | { type: "SET_SDK_EXPORTER_PROTOCOL"; protocol: string }
+ | { type: "SET_SDK_SAMPLER_TYPE"; samplerType: string }
+ | { type: "SET_SDK_SAMPLER_ROOT"; root: string }
+ | { type: "SET_SDK_SAMPLER_RATIO"; ratio: number }
+ | { type: "UPDATE_SDK_BATCH_SETTING"; key: string; value: number }
+ | { type: "RESET" }
+ | { type: "MARK_INITIALIZED" };
+
+export interface Template {
+ id: string;
+ name: string;
+ description: string;
+ category: "framework" | "experimental" | "semconv" | "custom";
+ instrumentations: string[];
+ configOverrides?: Record;
+}
+
+export interface ShareConfig {
+ v: string;
+ i: string[];
+ c: Record;
+ f?: "properties" | "env";
+}
+
+export interface ImportConfig {
+ v: string;
+ i: string[];
+}
diff --git a/ecosystem-explorer/src/types/sdk.ts b/ecosystem-explorer/src/types/sdk.ts
new file mode 100644
index 0000000..3b4902f
--- /dev/null
+++ b/ecosystem-explorer/src/types/sdk.ts
@@ -0,0 +1,128 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Runtime state stored in the builder context
+export interface SdkConfig {
+ fileFormat: string;
+ propagators: string[];
+ tracerProvider: {
+ exporterType: string;
+ exporterEndpoint: string;
+ exporterProtocol: string;
+ samplerType: string;
+ samplerRoot: string;
+ samplerRatio: number;
+ batchScheduleDelay: number;
+ batchExportTimeout: number;
+ batchMaxQueueSize: number;
+ batchMaxExportBatchSize: number;
+ };
+}
+
+// sdk-options.json shape — source of truth for defaults and UI metadata
+export interface SdkOptions {
+ schema_version: string;
+ defaults: SdkDefaults;
+ sections: SdkSections;
+}
+
+export interface SdkDefaults {
+ file_format: string;
+ propagators: string[];
+ tracer_provider: {
+ exporter_type: string;
+ exporters: Record>;
+ sampler: {
+ type: string;
+ root: string;
+ ratio: number;
+ };
+ batch_processor: {
+ schedule_delay: number;
+ export_timeout: number;
+ max_queue_size: number;
+ max_export_batch_size: number;
+ };
+ };
+}
+
+export interface SdkSections {
+ propagators: SdkPropagatorSection;
+ tracer_provider: SdkTracerProviderSection;
+}
+
+export interface SdkPropagatorSection {
+ name: string;
+ description: string;
+ options: SdkPropagatorOption[];
+}
+
+export interface SdkPropagatorOption {
+ id: string;
+ name: string;
+ description: string;
+}
+
+export interface SdkTracerProviderSection {
+ name: string;
+ description: string;
+ exporter: SdkExporterConfig;
+ sampler: SdkSamplerConfig;
+ batch_processor: SdkBatchProcessorConfig;
+}
+
+export interface SdkExporterConfig {
+ name: string;
+ description: string;
+ options: SdkExporterOption[];
+}
+
+export interface SdkExporterOption {
+ id: string;
+ name: string;
+ description: string;
+ settings: SdkSettingField[];
+}
+
+export interface SdkSamplerConfig {
+ name: string;
+ description: string;
+ options: SdkSamplerOption[];
+}
+
+export interface SdkSamplerOption {
+ id: string;
+ name: string;
+ description: string;
+ has_root?: boolean;
+ root_options?: string[];
+ has_ratio?: boolean;
+}
+
+export interface SdkBatchProcessorConfig {
+ name: string;
+ description: string;
+ settings: SdkSettingField[];
+}
+
+export interface SdkSettingField {
+ name: string;
+ label: string;
+ description: string;
+ type: "string" | "integer" | "enum";
+ options?: string[];
+ min?: number;
+}
diff --git a/ecosystem-registry/configuration/schema/opentelemetry_configuration.yaml b/ecosystem-registry/configuration/schema/opentelemetry_configuration.yaml
new file mode 100644
index 0000000..1a9499b
--- /dev/null
+++ b/ecosystem-registry/configuration/schema/opentelemetry_configuration.yaml
@@ -0,0 +1,104 @@
+type: object
+additionalProperties: true
+properties:
+ file_format:
+ type: string
+ description: |
+ The file format version.
+ Represented as a string including the semver major, minor version numbers (and optionally the meta tag). For example: "0.4", "1.0-rc.2", "1.0" (after stable release).
+ See https://github.com/open-telemetry/opentelemetry-configuration/blob/main/VERSIONING.md for more details.
+ The yaml format is documented at https://github.com/open-telemetry/opentelemetry-configuration/tree/main/schema
+ disabled:
+ type:
+ - boolean
+ - "null"
+ description: |
+ Configure if the SDK is disabled or not.
+ defaultBehavior: false is used
+ log_level:
+ $ref: common.yaml#/$defs/SeverityNumber
+ description: |
+ Configure the log level of the internal logger used by the SDK.
+ defaultBehavior: INFO is used
+ attribute_limits:
+ $ref: "#/$defs/AttributeLimits"
+ description: |
+ Configure general attribute limits. See also tracer_provider.limits, logger_provider.limits.
+ defaultBehavior: default values as described in AttributeLimits are used
+ logger_provider:
+ $ref: "#/$defs/LoggerProvider"
+ description: |
+ Configure logger provider.
+ defaultBehavior: a noop logger provider is used
+ meter_provider:
+ $ref: "#/$defs/MeterProvider"
+ description: |
+ Configure meter provider.
+ defaultBehavior: a noop meter provider is used
+ propagator:
+ $ref: "#/$defs/Propagator"
+ description: |
+ Configure text map context propagators.
+ defaultBehavior: a noop propagator is used
+ tracer_provider:
+ $ref: "#/$defs/TracerProvider"
+ description: |
+ Configure tracer provider.
+ defaultBehavior: a noop tracer provider is used
+ resource:
+ $ref: "#/$defs/Resource"
+ description: |
+ Configure resource for all signals.
+ defaultBehavior: the default resource is used
+ instrumentation/development:
+ $ref: "#/$defs/ExperimentalInstrumentation"
+ description: |
+ Configure instrumentation.
+ defaultBehavior: instrumentation defaults are used
+ distribution:
+ $ref: "#/$defs/Distribution"
+ description: |
+ Defines configuration parameters specific to a particular OpenTelemetry distribution or vendor.
+ defaultBehavior: distribution defaults are used
+required:
+ - file_format
+$defs:
+ AttributeLimits:
+ type: object
+ additionalProperties: false
+ properties:
+ attribute_value_length_limit:
+ type:
+ - integer
+ - "null"
+ minimum: 0
+ description: |
+ Configure max attribute value size.
+ Value must be non-negative.
+ defaultBehavior: there is no limit
+ attribute_count_limit:
+ type:
+ - integer
+ - "null"
+ minimum: 0
+ description: |
+ Configure max attribute count.
+ Value must be non-negative.
+ defaultBehavior: 128 is used
+ LoggerProvider:
+ $ref: logger_provider.yaml
+ MeterProvider:
+ $ref: meter_provider.yaml
+ TracerProvider:
+ $ref: tracer_provider.yaml
+ Propagator:
+ $ref: propagator.yaml
+ Resource:
+ $ref: resource.yaml
+ ExperimentalInstrumentation:
+ $ref: instrumentation.yaml
+ Distribution:
+ type: object
+ additionalProperties:
+ type: object
+ minProperties: 1
diff --git a/ecosystem-registry/configuration/schema/propagator.yaml b/ecosystem-registry/configuration/schema/propagator.yaml
new file mode 100644
index 0000000..48e99cb
--- /dev/null
+++ b/ecosystem-registry/configuration/schema/propagator.yaml
@@ -0,0 +1,62 @@
+type: object
+additionalProperties: false
+properties:
+ composite:
+ type: array
+ minItems: 1
+ items:
+ $ref: "#/$defs/TextMapPropagator"
+ description: |
+ Configure the propagators in the composite text map propagator.
+ Built-in propagator keys include: tracecontext, baggage, b3, b3multi.
+ defaultBehavior: a noop propagator is used
+ composite_list:
+ type:
+ - string
+ - "null"
+ description: |
+ Configure the propagators as a comma-separated string (matches OTEL_PROPAGATORS format).
+ Entries are appended to .composite with duplicates filtered out.
+ defaultBehavior: a noop propagator is used
+$defs:
+ TextMapPropagator:
+ type: object
+ additionalProperties:
+ type:
+ - object
+ - "null"
+ minProperties: 1
+ maxProperties: 1
+ properties:
+ tracecontext:
+ $ref: "#/$defs/TraceContextPropagator"
+ description: Include the W3C trace context propagator.
+ baggage:
+ $ref: "#/$defs/BaggagePropagator"
+ description: Include the W3C baggage propagator.
+ b3:
+ $ref: "#/$defs/B3Propagator"
+ description: Include the Zipkin B3 single-header propagator.
+ b3multi:
+ $ref: "#/$defs/B3MultiPropagator"
+ description: Include the Zipkin B3 multi-header propagator.
+ TraceContextPropagator:
+ type:
+ - object
+ - "null"
+ additionalProperties: false
+ BaggagePropagator:
+ type:
+ - object
+ - "null"
+ additionalProperties: false
+ B3Propagator:
+ type:
+ - object
+ - "null"
+ additionalProperties: false
+ B3MultiPropagator:
+ type:
+ - object
+ - "null"
+ additionalProperties: false
diff --git a/ecosystem-registry/configuration/schema/tracer_provider.yaml b/ecosystem-registry/configuration/schema/tracer_provider.yaml
new file mode 100644
index 0000000..c9a26a6
--- /dev/null
+++ b/ecosystem-registry/configuration/schema/tracer_provider.yaml
@@ -0,0 +1,237 @@
+type: object
+additionalProperties: false
+properties:
+ processors:
+ type: array
+ minItems: 1
+ items:
+ $ref: "#/$defs/SpanProcessor"
+ description: Configure span processors.
+ limits:
+ $ref: "#/$defs/SpanLimits"
+ description: Configure span limits. See also attribute_limits.
+ defaultBehavior: default values as described in SpanLimits are used
+ sampler:
+ $ref: "#/$defs/Sampler"
+ description: |
+ Configure the sampler.
+ defaultBehavior: parent based sampler with a root of always_on is used
+required:
+ - processors
+$defs:
+ BatchSpanProcessor:
+ type: object
+ additionalProperties: false
+ properties:
+ schedule_delay:
+ type:
+ - integer
+ - "null"
+ minimum: 0
+ description: |
+ Configure delay interval (in milliseconds) between two consecutive exports.
+ Value must be non-negative.
+ defaultBehavior: 5000 is used
+ export_timeout:
+ type:
+ - integer
+ - "null"
+ minimum: 0
+ description: |
+ Configure maximum allowed time (in milliseconds) to export data.
+ Value must be non-negative. A value of 0 indicates no limit (infinity).
+ defaultBehavior: 30000 is used
+ max_queue_size:
+ type:
+ - integer
+ - "null"
+ exclusiveMinimum: 0
+ description: |
+ Configure maximum queue size. Value must be positive.
+ defaultBehavior: 2048 is used
+ max_export_batch_size:
+ type:
+ - integer
+ - "null"
+ exclusiveMinimum: 0
+ description: |
+ Configure maximum batch size. Value must be positive.
+ defaultBehavior: 512 is used
+ exporter:
+ $ref: "#/$defs/SpanExporter"
+ description: Configure exporter.
+ required:
+ - exporter
+ Sampler:
+ type: object
+ additionalProperties:
+ type:
+ - object
+ - "null"
+ minProperties: 1
+ maxProperties: 1
+ properties:
+ always_off:
+ $ref: "#/$defs/AlwaysOffSampler"
+ description: Configure sampler to be always_off.
+ always_on:
+ $ref: "#/$defs/AlwaysOnSampler"
+ description: Configure sampler to be always_on.
+ parent_based:
+ $ref: "#/$defs/ParentBasedSampler"
+ description: Configure sampler to be parent_based.
+ trace_id_ratio_based:
+ $ref: "#/$defs/TraceIdRatioBasedSampler"
+ description: Configure sampler to be trace_id_ratio_based.
+ AlwaysOffSampler:
+ type:
+ - object
+ - "null"
+ additionalProperties: false
+ AlwaysOnSampler:
+ type:
+ - object
+ - "null"
+ additionalProperties: false
+ ParentBasedSampler:
+ type:
+ - object
+ - "null"
+ additionalProperties: false
+ properties:
+ root:
+ $ref: "#/$defs/Sampler"
+ description: |
+ Configure root sampler.
+ defaultBehavior: always_on is used
+ remote_parent_sampled:
+ $ref: "#/$defs/Sampler"
+ description: Configure remote_parent_sampled sampler.
+ defaultBehavior: always_on is used
+ remote_parent_not_sampled:
+ $ref: "#/$defs/Sampler"
+ description: Configure remote_parent_not_sampled sampler.
+ defaultBehavior: always_off is used
+ local_parent_sampled:
+ $ref: "#/$defs/Sampler"
+ description: Configure local_parent_sampled sampler.
+ defaultBehavior: always_on is used
+ local_parent_not_sampled:
+ $ref: "#/$defs/Sampler"
+ description: Configure local_parent_not_sampled sampler.
+ defaultBehavior: always_off is used
+ TraceIdRatioBasedSampler:
+ type:
+ - object
+ - "null"
+ additionalProperties: false
+ properties:
+ ratio:
+ type:
+ - number
+ - "null"
+ minimum: 0
+ maximum: 1
+ description: |
+ Configure trace_id_ratio.
+ defaultBehavior: 1.0 is used
+ SimpleSpanProcessor:
+ type: object
+ additionalProperties: false
+ properties:
+ exporter:
+ $ref: "#/$defs/SpanExporter"
+ description: Configure exporter.
+ required:
+ - exporter
+ SpanExporter:
+ type: object
+ additionalProperties:
+ type:
+ - object
+ - "null"
+ minProperties: 1
+ maxProperties: 1
+ properties:
+ otlp_http:
+ $ref: common.yaml#/$defs/OtlpHttpExporter
+ description: Configure exporter to be OTLP with HTTP transport.
+ otlp_grpc:
+ $ref: common.yaml#/$defs/OtlpGrpcExporter
+ description: Configure exporter to be OTLP with gRPC transport.
+ console:
+ $ref: common.yaml#/$defs/ConsoleExporter
+ description: Configure exporter to be console.
+ SpanLimits:
+ type: object
+ additionalProperties: false
+ properties:
+ attribute_value_length_limit:
+ type:
+ - integer
+ - "null"
+ minimum: 0
+ description: |
+ Configure max attribute value size. Overrides .attribute_limits.attribute_value_length_limit.
+ Value must be non-negative.
+ defaultBehavior: there is no limit
+ attribute_count_limit:
+ type:
+ - integer
+ - "null"
+ minimum: 0
+ description: |
+ Configure max attribute count. Overrides .attribute_limits.attribute_count_limit.
+ Value must be non-negative.
+ defaultBehavior: 128 is used
+ event_count_limit:
+ type:
+ - integer
+ - "null"
+ minimum: 0
+ description: |
+ Configure max span event count.
+ Value must be non-negative.
+ defaultBehavior: 128 is used
+ link_count_limit:
+ type:
+ - integer
+ - "null"
+ minimum: 0
+ description: |
+ Configure max span link count.
+ Value must be non-negative.
+ defaultBehavior: 128 is used
+ event_attribute_count_limit:
+ type:
+ - integer
+ - "null"
+ minimum: 0
+ description: |
+ Configure max attributes per span event.
+ Value must be non-negative.
+ defaultBehavior: 128 is used
+ link_attribute_count_limit:
+ type:
+ - integer
+ - "null"
+ minimum: 0
+ description: |
+ Configure max attributes per span link.
+ Value must be non-negative.
+ defaultBehavior: 128 is used
+ SpanProcessor:
+ type: object
+ additionalProperties:
+ type:
+ - object
+ - "null"
+ minProperties: 1
+ maxProperties: 1
+ properties:
+ batch:
+ $ref: "#/$defs/BatchSpanProcessor"
+ description: Configure a batch span processor.
+ simple:
+ $ref: "#/$defs/SimpleSpanProcessor"
+ description: Configure a simple span processor.