.NET SDK Overview
The Shield360 .NET SDK provides OpenTelemetry-native observability for .NET applications that use large language models. It builds on the official OpenTelemetry .NET packages: one call sets up tracing and metrics, a Microsoft.Extensions.AI middleware records every chat call, and the GenAI telemetry already built into the .NET AI libraries is turned on and collected.
Supported libraries
Section titled “Supported libraries”UseShield360() middleware for any IChatClient: chat spans, streaming, token usage, cost and tool calls.
Chat completion spans and metrics from the OpenAI package, including Azure.AI.OpenAI, which is built on it.
Chat completion spans and metrics from Azure.AI.Inference.
Chat and function spans from Semantic Kernel, with no changes to your kernel.
What gets collected
Section titled “What gets collected”- Distributed traces -
chat {model}spans with request settings, response ID and model, finish reasons, token counts and cost - OpenTelemetry metrics -
gen_ai.client.token.usage,gen_ai.client.operation.durationandgen_ai.usage.cost - Streaming metrics -
gen_ai.server.time_to_first_token,gen_ai.server.time_per_output_token, time to first chunk and time per chunk - Cost tracking -
gen_ai.usage.cost, computed from the same pricing data as the Python and TypeScript SDKs - Message content, only when you ask - prompts and responses are recorded only with content capture on, and stored only if your Data retention setting allows it
Prerequisites
Section titled “Prerequisites”- .NET 8 or later (the package also targets .NET Standard 2.0)
- A Shield360 deployment, or any OpenTelemetry backend that accepts OTLP over HTTP
Get started
Section titled “Get started”dotnet add package Shield360Initialize Shield360 once at start-up, before you create any AI client. The .NET AI libraries read their telemetry switches once, when the first client is created.
using Shield360;
using var shield360 = Shield360Sdk.Init(new Shield360Options{ OtlpEndpoint = "http://127.0.0.1:4318", ApplicationName = "my-ai-app", Environment = "production",});Disposing the handle sends any buffered telemetry and shuts Shield360 down.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddShield360(o =>{ o.ApplicationName = "orders-api"; o.Environment = builder.Environment.EnvironmentName;});AddShield360 returns the OpenTelemetry builder, so you can add more instrumentation, such as .WithTracing(t => t.AddAspNetCoreInstrumentation()).
builder.Services.AddOpenTelemetry() .WithTracing(t => t.AddShield360Instrumentation().AddOtlpExporter()) .WithMetrics(m => m.AddShield360Instrumentation().AddOtlpExporter());Add Shield360 before your exporter, so its span changes are exported.
Add UseShield360() to a Microsoft.Extensions.AI pipeline:
using Microsoft.Extensions.AI;using OpenAI;
IChatClient client = new OpenAIClient(apiKey) .GetChatClient("gpt-4o-mini") .AsIChatClient() .AsBuilder() .UseShield360() .Build();
var response = await client.GetResponseAsync("What is OpenTelemetry?");With dependency injection, add it to the registration:
builder.Services.AddChatClient(services => innerClient).UseShield360();UseShield360() does the job of UseOpenTelemetry(). Use one of them in a pipeline, not both.
Semantic Kernel, the OpenAI SDK and Azure AI Inference need no code changes: once Shield360 is initialized, their spans and metrics are collected.
using var shield360 = Shield360Sdk.Init(new Shield360Options { ApplicationName = "planner" });
var kernel = Kernel.CreateBuilder() .AddOpenAIChatCompletion("gpt-4o-mini", apiKey) .Build();
Console.WriteLine(await kernel.InvokePromptAsync("Plan a three-step rollout."));How library telemetry is collected
Section titled “How library telemetry is collected”Shield360 listens to each library’s ActivitySource and Meter, and turns on the AppContext switch the library needs:
| Library | ActivitySource and Meter | Switch |
|---|---|---|
Shield360 UseShield360() | Shield360 | - |
Microsoft.Extensions.AI UseOpenTelemetry() | Experimental.Microsoft.Extensions.AI | - |
| OpenAI .NET SDK and Azure.AI.OpenAI | OpenAI.* | OpenAI.Experimental.EnableOpenTelemetry |
| Azure.AI.Inference | Azure.AI.Inference.* | Azure.Experimental.EnableActivitySource |
| Semantic Kernel | Microsoft.SemanticKernel* | Microsoft.SemanticKernel.Experimental.GenAI.EnableOTelDiagnostics |
Spans from these libraries get service.name, deployment.environment and gen_ai.usage.cost added, and message content removed unless content capture is on. When GenAI spans nest, such as Semantic Kernel calling the OpenAI SDK, only the outermost span carries the cost, so it is not counted twice.
To leave a library out, list it in DisabledInstrumentors or SHIELD360_DISABLED_INSTRUMENTORS: microsoft-extensions-ai, openai, azure-ai-inference or semantic-kernel.
Configuration
Section titled “Configuration”Every option falls back to its environment variable, then its default:
| Option | Environment variable | Description | Default |
|---|---|---|---|
OtlpEndpoint | OTEL_EXPORTER_OTLP_ENDPOINT | OTLP/HTTP endpoint | http://127.0.0.1:4318 |
OtlpHeaders | OTEL_EXPORTER_OTLP_HEADERS | Headers for every export | none |
ApiKey | SHIELD360_API_KEY | Shield360 API key | none |
Shield360Url | SHIELD360_URL | Shield360 URL; with an API key and no endpoint, telemetry goes to the Shield360 ingest route | none |
ApplicationName | OTEL_SERVICE_NAME | Recorded as service.name | default |
Environment | OTEL_DEPLOYMENT_ENVIRONMENT | Recorded as deployment.environment | default |
CaptureMessageContent | OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT | Record prompts, responses and tool arguments | false |
PricingJson | SHIELD360_PRICING_JSON | Pricing data as a URL, file path or JSON string | bundled pricing |
DisableMetrics | SHIELD360_DISABLE_METRICS | Do not export metrics | false |
DisableBatch | SHIELD360_DISABLE_BATCH | Export each span as it ends | false |
DisabledInstrumentors | SHIELD360_DISABLED_INSTRUMENTORS | Libraries to leave out | none |
CustomSpanAttributes | SHIELD360_CUSTOM_SPAN_ATTRIBUTES | Attributes added to every GenAI span | none |
To send telemetry straight to Shield360, set the URL and API key and leave the endpoint unset:
export SHIELD360_URL=https://app.shield360.aiexport SHIELD360_API_KEY=your-api-keyThe API key is sent only over HTTPS, or over HTTP to localhost, and is never logged.
Rule engine
Section titled “Rule engine”Evaluate trace attributes against your Shield360 rules to get the matching contexts, prompts or evaluations. This does not need Init.
using Shield360.RuleEngine;
var result = await Shield360Sdk.EvaluateRuleAsync(new EvaluateRuleOptions{ EntityType = RuleEntityType.Context, Fields = new Dictionary<string, object?> { ["gen_ai.system"] = "openai", ["gen_ai.request.model"] = "gpt-4o", }, IncludeEntityData = true,});