Skip to content

Commit dd60f24

Browse files
author
Xiting Zhang
committed
Add telemetry sample folder with ExplicitTracingSample and GlobalTracingSample
1 parent 8ae4b6b commit dd60f24

6 files changed

Lines changed: 191 additions & 32 deletions

File tree

.vscode/cspell.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,6 +1472,8 @@
14721472
"FILLER",
14731473
"foundry",
14741474
"FOUNDRY",
1475+
"genai",
1476+
"GENAI",
14751477
"Unpooled",
14761478
"viseme",
14771479
"VISEME",

sdk/voicelive/azure-ai-voicelive/README.md

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -169,19 +169,23 @@ For easier learning, explore these focused samples in order:
169169
- Execute functions locally and return results
170170
- Continue conversation with function results
171171

172-
7. **TelemetrySample.java** - OpenTelemetry tracing integration
173-
- Automatic tracing via GlobalOpenTelemetry (zero-config)
172+
7. **telemetry/ExplicitTracingSample.java** - Explicit OpenTelemetry tracing
174173
- Explicit OpenTelemetry instance via builder
175-
- Span structure and session-level attributes
174+
- Content recording with `--enable-recording` flag
175+
- Custom console span exporter
176176
- Azure Monitor integration example
177177

178-
8. **MCPSample.java** - Model Context Protocol (MCP) tool integration
178+
8. **telemetry/GlobalTracingSample.java** - Automatic tracing via GlobalOpenTelemetry
179+
- Zero builder configuration — uses `buildAndRegisterGlobal()`
180+
- Same span output as explicit tracing
181+
182+
9. **MCPSample.java** - Model Context Protocol (MCP) tool integration
179183
- Configure MCP servers for external tool access
180184
- Handle MCP call events and tool execution
181185
- Handle MCP approval requests for tool calls
182186
- Process MCP call results and continue conversations
183187

184-
9. **AgentV2Sample.java** - Azure AI Foundry agent session
188+
10. **AgentV2Sample.java** - Azure AI Foundry agent session
185189
- Connect directly to an Azure AI Foundry agent via AgentSessionConfig
186190
- Real-time audio capture and playback
187191
- Sequence number based audio for interrupt handling
@@ -485,7 +489,10 @@ The SDK has built-in [OpenTelemetry](https://opentelemetry.io/) tracing that emi
485489
486490
#### Automatic tracing (recommended)
487491
488-
If the [OpenTelemetry Java agent](https://opentelemetry.io/docs/languages/java/automatic/) is attached, or `GlobalOpenTelemetry` is configured, tracing works automatically with no code changes:
492+
When no `.openTelemetry()` is called on the builder, the SDK defaults to `GlobalOpenTelemetry.getOrNoop()`
493+
tracing is automatically active when a global OpenTelemetry instance exists (e.g., via the
494+
[OpenTelemetry Java agent](https://opentelemetry.io/docs/languages/java/automatic/) or
495+
`OpenTelemetrySdk.builder().buildAndRegisterGlobal()`), and is a zero-cost no-op otherwise:
489496
490497
```java com.azure.ai.voicelive.tracing.automatic
491498
// No special configuration needed — tracing is picked up from GlobalOpenTelemetry
@@ -497,7 +504,8 @@ VoiceLiveAsyncClient client = new VoiceLiveClientBuilder()
497504
498505
#### Explicit OpenTelemetry instance
499506
500-
Provide your own `OpenTelemetry` instance to control trace export:
507+
Pass your own `OpenTelemetry` instance directly to the builder for full control. This is useful
508+
when you want different clients to use different tracer configurations:
501509
502510
```java com.azure.ai.voicelive.tracing.explicit
503511
VoiceLiveAsyncClient client = new VoiceLiveClientBuilder()
@@ -514,29 +522,30 @@ When tracing is active, the following span hierarchy is emitted for each voice s
514522
```
515523
connect gpt-4o-realtime-preview ← session lifetime span
516524
├── send session.update ← one span per sent event
525+
├── send input_audio_buffer.append
517526
├── send response.create
518527
├── recv session.created ← one span per received event
528+
├── recv session.updated
519529
├── recv response.audio.delta
520530
├── recv response.done ← includes token usage attributes
531+
├── recv rate_limits.updated ← rate limit info
521532
└── close
522533
```
523534
524-
**Session-level attributes** (on the connect span):
525-
- `gen_ai.system``az.ai.voicelive`
526-
- `gen_ai.provider.name``microsoft.foundry`
527-
- `gen_ai.request.model` — Model name (e.g., `gpt-4o-realtime-preview`)
528-
- `server.address` — Service endpoint
535+
**Common attributes** (on all spans): `gen_ai.system`, `gen_ai.operation.name`, `gen_ai.provider.name`, `gen_ai.request.model`, `az.namespace`, `server.address`, `server.port`
536+
537+
**Session-level attributes** (on the connect span, flushed at session close):
529538
- `gen_ai.voice.session_id` — Voice session ID
530-
- `gen_ai.conversation.id` — Conversation ID
531-
- `gen_ai.response.id` — Latest response ID
532-
- `gen_ai.response.finish_reasons` — Response status list (e.g. `[`"completed"`]`)
533-
- `gen_ai.agent.name` / `gen_ai.agent.id` / `gen_ai.agent.thread_id` — Agent metadata when using agent sessions
534-
- `gen_ai.system_instructions` / `gen_ai.request.temperature` / `gen_ai.request.max_output_tokens` / `gen_ai.request.tools` — Session config tracked from `session.update`
539+
- `gen_ai.voice.input_audio_format` / `gen_ai.voice.output_audio_format` — Audio formats (e.g., `pcm16`)
540+
- `gen_ai.voice.input_sample_rate` — Input audio sampling rate (Hz)
535541
- `gen_ai.voice.turn_count` — Completed response turns
536542
- `gen_ai.voice.interruption_count` — User interruptions
537543
- `gen_ai.voice.audio_bytes_sent` / `gen_ai.voice.audio_bytes_received` — Audio payload bytes
538544
- `gen_ai.voice.first_token_latency_ms` — Time to first audio response
539-
- `gen_ai.voice.mcp.call_count` / `gen_ai.voice.mcp.list_tools_count` — MCP operation counters
545+
- `gen_ai.conversation.id` — Conversation ID
546+
- `gen_ai.response.id` / `gen_ai.response.finish_reasons` — Last response metadata
547+
- `gen_ai.system_instructions` / `gen_ai.request.temperature` / `gen_ai.request.max_output_tokens` / `gen_ai.request.tools` — Session config from `session.update`
548+
- `gen_ai.agent.name` / `gen_ai.agent.id` / `gen_ai.agent.version` / `gen_ai.agent.project_name` / `gen_ai.agent.thread_id` — Agent metadata (when using `AgentSessionConfig`)
540549
541550
#### Content recording
542551
@@ -556,15 +565,18 @@ VoiceLiveAsyncClient client = new VoiceLiveClientBuilder()
556565
// (legacy fallback) AZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED=true
557566
```
558567
559-
> See `TelemetrySample.java` for complete tracing examples including Azure Monitor integration.
568+
> See `telemetry/ExplicitTracingSample.java` and `telemetry/GlobalTracingSample.java` for complete tracing examples.
560569
>
561-
> **Run the telemetry sample** to see tracing in action:
570+
> **Run the telemetry samples** to see tracing in action:
562571
> ```bash
563-
> # Tracing only (prints span names and attributes):
564-
> mvn exec:java -Dexec.mainClass="com.azure.ai.voicelive.TelemetrySample" -Dexec.classpathScope=test -Dexec.args="--enable-tracing"
572+
> # Explicit tracing (prints span names and attributes):
573+
> mvn exec:java -Dexec.mainClass="com.azure.ai.voicelive.telemetry.ExplicitTracingSample" -Dexec.classpathScope=test -Dexec.args="--enable-tracing"
574+
>
575+
> # Explicit tracing + content recording (also prints full JSON payloads):
576+
> mvn exec:java -Dexec.mainClass="com.azure.ai.voicelive.telemetry.ExplicitTracingSample" -Dexec.classpathScope=test -Dexec.args="--enable-tracing --enable-recording"
565577
>
566-
> # Tracing + content recording (also prints full JSON payloads):
567-
> mvn exec:java -Dexec.mainClass="com.azure.ai.voicelive.TelemetrySample" -Dexec.classpathScope=test -Dexec.args="--enable-tracing --enable-recording"
578+
> # Automatic tracing via GlobalOpenTelemetry:
579+
> mvn exec:java -Dexec.mainClass="com.azure.ai.voicelive.telemetry.GlobalTracingSample" -Dexec.classpathScope=test
568580
> ```
569581
>
570582
> Sample output with `--enable-tracing`:

sdk/voicelive/azure-ai-voicelive/checkstyle-suppressions.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
<suppress files="com.azure.ai.voicelive.VoiceLiveTracerTest.java" checks="IllegalImportCheck" />
1313
<suppress files="com.azure.ai.voicelive.VoiceLiveClientBuilderTest.java" checks="IllegalImportCheck" />
1414
<suppress files="com.azure.ai.voicelive.ReadmeSamples.java" checks="IllegalImportCheck" />
15-
<suppress files="com.azure.ai.voicelive.TelemetrySample.java" checks="IllegalImportCheck" />
15+
<suppress files="com.azure.ai.voicelive.telemetry.ExplicitTracingSample.java" checks="IllegalImportCheck" />
16+
<suppress files="com.azure.ai.voicelive.telemetry.GlobalTracingSample.java" checks="IllegalImportCheck" />
1617
<suppress files="com.azure.ai.voicelive.VoiceAssistantSample.java" checks="IllegalImportCheck" />
1718
<suppress files="com.azure.ai.voicelive.telemetry.VoiceLiveTelemetryAttributeKeys.java" checks="IllegalImportCheck" />
1819

sdk/voicelive/azure-ai-voicelive/src/main/java/com/azure/ai/voicelive/VoiceLiveTracer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ void traceClose() {
556556
}
557557

558558
/**
559-
* Traces a raw (unparseable) receive operation. Used when a message fails to parse or for raw events.
559+
* Traces a raw receive operation. Used when a message fails to parse or for raw events.
560560
*
561561
* @param rawPayload The raw JSON payload string.
562562
*/

sdk/voicelive/azure-ai-voicelive/src/samples/java/com/azure/ai/voicelive/TelemetrySample.java renamed to sdk/voicelive/azure-ai-voicelive/src/samples/java/com/azure/ai/voicelive/telemetry/ExplicitTracingSample.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
package com.azure.ai.voicelive;
4+
package com.azure.ai.voicelive.telemetry;
55

6+
import com.azure.ai.voicelive.VoiceLiveAsyncClient;
7+
import com.azure.ai.voicelive.VoiceLiveClientBuilder;
68
import com.azure.ai.voicelive.models.ClientEventSessionUpdate;
79
import com.azure.ai.voicelive.models.InteractionModality;
810
import com.azure.ai.voicelive.models.SessionUpdateResponseDone;
@@ -38,13 +40,13 @@
3840
* <p><strong>How to Run:</strong></p>
3941
* <pre>{@code
4042
* # Basic (no tracing):
41-
* mvn exec:java -Dexec.mainClass="com.azure.ai.voicelive.TelemetrySample" -Dexec.classpathScope=test
43+
* mvn exec:java -Dexec.mainClass="com.azure.ai.voicelive.telemetry.ExplicitTracingSample" -Dexec.classpathScope=test
4244
*
4345
* # With OpenTelemetry tracing enabled:
44-
* mvn exec:java -Dexec.mainClass="com.azure.ai.voicelive.TelemetrySample" -Dexec.classpathScope=test -Dexec.args="--enable-tracing"
46+
* mvn exec:java -Dexec.mainClass="com.azure.ai.voicelive.telemetry.ExplicitTracingSample" -Dexec.classpathScope=test -Dexec.args="--enable-tracing"
4547
*
4648
* # With tracing + JSON payload content recording:
47-
* mvn exec:java -Dexec.mainClass="com.azure.ai.voicelive.TelemetrySample" -Dexec.classpathScope=test -Dexec.args="--enable-tracing --enable-recording"
49+
* mvn exec:java -Dexec.mainClass="com.azure.ai.voicelive.telemetry.ExplicitTracingSample" -Dexec.classpathScope=test -Dexec.args="--enable-tracing --enable-recording"
4850
* }</pre>
4951
*
5052
* <p><strong>Span Structure:</strong></p>
@@ -84,7 +86,7 @@
8486
* .build();
8587
* }</pre>
8688
*/
87-
public final class TelemetrySample {
89+
public final class ExplicitTracingSample {
8890

8991
/**
9092
* Main method to run the telemetry sample.
@@ -187,7 +189,7 @@ public static void main(String[] args) throws InterruptedException {
187189
}
188190
}
189191

190-
private TelemetrySample() {
192+
private ExplicitTracingSample() {
191193
}
192194

193195
/**
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.ai.voicelive.telemetry;
5+
6+
import com.azure.ai.voicelive.VoiceLiveAsyncClient;
7+
import com.azure.ai.voicelive.VoiceLiveClientBuilder;
8+
import com.azure.ai.voicelive.models.ClientEventSessionUpdate;
9+
import com.azure.ai.voicelive.models.InteractionModality;
10+
import com.azure.ai.voicelive.models.SessionUpdateResponseDone;
11+
import com.azure.ai.voicelive.models.VoiceLiveSessionOptions;
12+
import com.azure.core.credential.KeyCredential;
13+
import io.opentelemetry.sdk.OpenTelemetrySdk;
14+
import io.opentelemetry.sdk.common.CompletableResultCode;
15+
import io.opentelemetry.sdk.trace.SdkTracerProvider;
16+
import io.opentelemetry.sdk.trace.data.EventData;
17+
import io.opentelemetry.sdk.trace.data.SpanData;
18+
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
19+
import io.opentelemetry.sdk.trace.export.SpanExporter;
20+
import reactor.core.publisher.Mono;
21+
22+
import java.util.Arrays;
23+
import java.util.Collection;
24+
import java.util.concurrent.CountDownLatch;
25+
import java.util.concurrent.TimeUnit;
26+
27+
/**
28+
* Sample demonstrating automatic tracing via {@code GlobalOpenTelemetry}.
29+
*
30+
* <p>Unlike {@link ExplicitTracingSample} which passes an explicit {@code OpenTelemetry} instance
31+
* to the builder, this sample registers a global instance with
32+
* {@code OpenTelemetrySdk.builder().buildAndRegisterGlobal()}. The VoiceLive client picks it
33+
* up automatically — no {@code .openTelemetry()} call is needed on the builder.</p>
34+
*
35+
* <p><strong>Environment Variables Required:</strong></p>
36+
* <ul>
37+
* <li>{@code AZURE_VOICELIVE_ENDPOINT} — The VoiceLive service endpoint URL</li>
38+
* <li>{@code AZURE_VOICELIVE_API_KEY} — The API key for authentication</li>
39+
* </ul>
40+
*
41+
* <p><strong>How to Run:</strong></p>
42+
* <pre>{@code
43+
* mvn exec:java -Dexec.mainClass="com.azure.ai.voicelive.telemetry.GlobalTracingSample" -Dexec.classpathScope=test
44+
* }</pre>
45+
*/
46+
public final class GlobalTracingSample {
47+
48+
public static void main(String[] args) throws InterruptedException {
49+
String endpoint = System.getenv("AZURE_VOICELIVE_ENDPOINT");
50+
String apiKey = System.getenv("AZURE_VOICELIVE_API_KEY");
51+
52+
if (endpoint == null || apiKey == null) {
53+
System.err.println("Please set AZURE_VOICELIVE_ENDPOINT and AZURE_VOICELIVE_API_KEY environment variables");
54+
return;
55+
}
56+
57+
// 1. Register a global OpenTelemetry instance BEFORE building any client.
58+
// In production, you'd use OtlpGrpcSpanExporter or Azure Monitor instead of ConsoleSpanExporter.
59+
// Alternatively, attach the OpenTelemetry Java agent (-javaagent:opentelemetry-javaagent.jar)
60+
// which does this automatically — no code needed at all.
61+
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
62+
.addSpanProcessor(SimpleSpanProcessor.create(new ConsoleSpanExporter()))
63+
.build();
64+
OpenTelemetrySdk.builder()
65+
.setTracerProvider(tracerProvider)
66+
.buildAndRegisterGlobal(); // <-- registers into GlobalOpenTelemetry
67+
System.out.println("GlobalOpenTelemetry registered (console exporter)");
68+
69+
// 2. Build client WITHOUT .openTelemetry() — it picks up GlobalOpenTelemetry automatically.
70+
VoiceLiveAsyncClient client = new VoiceLiveClientBuilder()
71+
.endpoint(endpoint)
72+
.credential(new KeyCredential(apiKey))
73+
.buildAsyncClient();
74+
75+
System.out.println("Starting voice session (automatic tracing)...");
76+
77+
CountDownLatch done = new CountDownLatch(1);
78+
79+
// 3. Run a short text-mode conversation — all operations are traced automatically.
80+
client.startSession("gpt-4o-realtime-preview")
81+
.flatMap(session -> {
82+
VoiceLiveSessionOptions options = new VoiceLiveSessionOptions()
83+
.setModalities(Arrays.asList(InteractionModality.TEXT))
84+
.setInstructions("You are a helpful assistant. Be concise.");
85+
86+
session.receiveEvents()
87+
.subscribe(
88+
event -> {
89+
System.out.println("Event: " + event.getType());
90+
if (event instanceof SessionUpdateResponseDone) {
91+
session.closeAsync().subscribe();
92+
done.countDown();
93+
}
94+
},
95+
error -> {
96+
System.err.println("Error: " + error.getMessage());
97+
done.countDown();
98+
}
99+
);
100+
101+
return session.sendEvent(new ClientEventSessionUpdate(options))
102+
.then(session.startResponse())
103+
.then(Mono.empty());
104+
})
105+
.subscribe();
106+
107+
done.await(30, TimeUnit.SECONDS);
108+
109+
// 4. Flush remaining spans.
110+
tracerProvider.close();
111+
}
112+
113+
private GlobalTracingSample() {
114+
}
115+
116+
/**
117+
* Minimal console exporter that prints span names, attributes, and events.
118+
*/
119+
private static final class ConsoleSpanExporter implements SpanExporter {
120+
121+
@Override
122+
public CompletableResultCode export(Collection<SpanData> spans) {
123+
for (SpanData span : spans) {
124+
System.out.printf("'%s' : %s%n", span.getName(), span.getAttributes());
125+
for (EventData event : span.getEvents()) {
126+
System.out.printf(" Event '%s': %s%n", event.getName(), event.getAttributes());
127+
}
128+
}
129+
return CompletableResultCode.ofSuccess();
130+
}
131+
132+
@Override
133+
public CompletableResultCode flush() {
134+
return CompletableResultCode.ofSuccess();
135+
}
136+
137+
@Override
138+
public CompletableResultCode shutdown() {
139+
return CompletableResultCode.ofSuccess();
140+
}
141+
}
142+
}

0 commit comments

Comments
 (0)