Skip to content

.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.

Microsoft.Extensions.AI

UseShield360() middleware for any IChatClient: chat spans, streaming, token usage, cost and tool calls.

OpenAI .NET SDK

Chat completion spans and metrics from the OpenAI package, including Azure.AI.OpenAI, which is built on it.

Azure AI Inference

Chat completion spans and metrics from Azure.AI.Inference.

Semantic Kernel

Chat and function spans from Semantic Kernel, with no changes to your kernel.

  • 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.duration and gen_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
  • .NET 8 or later (the package also targets .NET Standard 2.0)
  • A Shield360 deployment, or any OpenTelemetry backend that accepts OTLP over HTTP
Install the package
Terminal window
dotnet add package Shield360
Initialize Shield360

Initialize 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.

Instrument your chat client

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."));

Shield360 listens to each library’s ActivitySource and Meter, and turns on the AppContext switch the library needs:

LibraryActivitySource and MeterSwitch
Shield360 UseShield360()Shield360-
Microsoft.Extensions.AI UseOpenTelemetry()Experimental.Microsoft.Extensions.AI-
OpenAI .NET SDK and Azure.AI.OpenAIOpenAI.*OpenAI.Experimental.EnableOpenTelemetry
Azure.AI.InferenceAzure.AI.Inference.*Azure.Experimental.EnableActivitySource
Semantic KernelMicrosoft.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.

Every option falls back to its environment variable, then its default:

OptionEnvironment variableDescriptionDefault
OtlpEndpointOTEL_EXPORTER_OTLP_ENDPOINTOTLP/HTTP endpointhttp://127.0.0.1:4318
OtlpHeadersOTEL_EXPORTER_OTLP_HEADERSHeaders for every exportnone
ApiKeySHIELD360_API_KEYShield360 API keynone
Shield360UrlSHIELD360_URLShield360 URL; with an API key and no endpoint, telemetry goes to the Shield360 ingest routenone
ApplicationNameOTEL_SERVICE_NAMERecorded as service.namedefault
EnvironmentOTEL_DEPLOYMENT_ENVIRONMENTRecorded as deployment.environmentdefault
CaptureMessageContentOTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENTRecord prompts, responses and tool argumentsfalse
PricingJsonSHIELD360_PRICING_JSONPricing data as a URL, file path or JSON stringbundled pricing
DisableMetricsSHIELD360_DISABLE_METRICSDo not export metricsfalse
DisableBatchSHIELD360_DISABLE_BATCHExport each span as it endsfalse
DisabledInstrumentorsSHIELD360_DISABLED_INSTRUMENTORSLibraries to leave outnone
CustomSpanAttributesSHIELD360_CUSTOM_SPAN_ATTRIBUTESAttributes added to every GenAI spannone

To send telemetry straight to Shield360, set the URL and API key and leave the endpoint unset:

Terminal window
export SHIELD360_URL=https://app.shield360.ai
export SHIELD360_API_KEY=your-api-key

The API key is sent only over HTTPS, or over HTTP to localhost, and is never logged.

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,
});