cai.analyzers 0.30.0

dotnet add package cai.analyzers --version 0.30.0
                    
NuGet\Install-Package cai.analyzers -Version 0.30.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="cai.analyzers" Version="0.30.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="cai.analyzers" Version="0.30.0" />
                    
Directory.Packages.props
<PackageReference Include="cai.analyzers" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add cai.analyzers --version 0.30.0
                    
#r "nuget: cai.analyzers, 0.30.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package cai.analyzers@0.30.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=cai.analyzers&version=0.30.0
                    
Install as a Cake Addin
#tool nuget:?package=cai.analyzers&version=0.30.0
                    
Install as a Cake Tool

About

This has analyzer for CAI apis

Steps

  • Create a new asp.net web api project

  • Add HotChocolate.AspNetCore > 16.0.3, HotChocolate.Data >= 16.0.3, Wolverine > 3.4.0 and Cai.Abstraction

  • Setup HotChocolate and Wolverine as needed. Two helper functions have been provided - builder.addCai(...) and app.useCai(...)

  • Apply the attributes Rest, Query and Mutation as needed.

  • Apply RemoteHandler or Distributed to use Nats as the message broker in addition to Wolverine.

  • Apply AutoLog to log all requests.

  • Nats.Net#2.5.5 required

Known Issues

Due to a limitation with HotChocolate, ensure that there is at least 1 query for graphql to work. This will be addressed in a future update

Usage

Add the package with the following properties to cai.analyzers: OutputItemType="Analyzer" ReferenceOutputAssembly="false"

Breaking Changes

v0.20.0 is a breaking change.

  • Nats.Net v2.5.5 required
  • OpenTelemetry is required

Sample packages

  <ItemGroup>
    <PackageReference Include="HotChocolate.AspNetCore" Version="16.0.3" />
    <PackageReference Include="HotChocolate.Data" Version="16.0.3" />
    <PackageReference Include="NATS.Net" Version="2.8.0" />
    <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.15.3" />
    <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
    <PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.7.0-rc.1" />
    <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
    <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
    <PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.10.0-beta.1" />
    <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
    <PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />
    <PackageReference Include="Ulid" Version="1.4.1" />
    <PackageReference Include="WolverineFx" Version="5.39.2" />
  </ItemGroup>

Require configuration in app.config

Add the configuration below to your app.config. This is required by

  "otel": {
    "serviceName": "[change me]",
    "activitySourceName": "[change me]",
    "logs": [
      {
        "url": "http://localhost:4317",
        "protocol": "Grpc"
      },
      {
        "url": "console"
      }
    ],
    "traces": [
      {
        "url": "http://localhost:4317",
        "protocol": "Grpc"
      },
      {
        "url": "console"
      }
    ],
    "metrics": [
      {
        "url": "http://localhost:4317",
        "protocol": "Grpc"
      },
      {
        "url": "console"
      }
    ],
    "tags": [
      {
        "name": "app.name",
        "value": "[change me]"
      },
      {
        "name": "app.version",
        "value": "[change me]"
      }
    ]
  },
"nats": {
    "url": "nats://127.0.0.1:4222",
    "username": "u",
    "password": "p",
    "tlsMode": "Implicit",
    "services": {
      "defaultNamespace": "DefaultNamespace"
    }
  }

Nats subject namespaces

When nats.services.defaultNamespace is set and not empty, all subjects are prefixed with it at runtime (e.g. calc.add becomes CaiTest.calc.add) so that multiple apps can share one NATS server. Both the client and the server side use NatsService.getSubject(subject) so they always agree. The namespace is sanitized to NATS-safe subject characters (A-Za-z0-9_- and single . separators; everything else is dropped), so a misconfigured namespace can never produce an invalid subject.

Per attribute, prefixing can be turned off with prefixNamespace: false (default true) when a subject should be global:

[Distributed("global.topic", prefixNamespace: false)]
public record GlobalAction(...);

AutoLog

Applying [AutoLog] to a record enables structured request/response logging for all of its Query, Mutation and Rest endpoints. Request/response payloads are flattened into dotted attribute keys so they stay searchable in Seq instead of being written as a single value.ToString().

Each operation's payload is namespaced by its GraphQL field/alias path (or, for REST, the verb+path), so concurrent resolvers on one request don't overwrite each other. E.g. { a: add(...) b: add(...) } produces a.input.value1, b.input.value1, a.output.*, b.output.*; a single add produces add.input.value1. The session tag store is thread-safe (ConcurrentDictionary), so parallel HotChocolate resolvers no longer corrupt it.

[AutoLog("add", maxDepth: 2)]
[Distributed("calc.add")]
[Query<CalculatorResult>]
public record Add(int value1, int value2);
  • activityName - override the activity/action name (defaults to the endpoint name).
  • logRequest / logResponse - whether the input/output should be written (defaults to true).
  • disabled - set to true to disable logging for this record (defaults to false).
  • maxDepth - how deep nested objects are flattened:
    • 0 (default) - use the global default depth,
    • -1 - no limit,
    • any other value - fixed depth.

Global logging limits

The generator cannot read appsettings.json at compile time, so global defaults are provided as MSBuild properties in the consuming project (or a Directory.Build.props):

<PropertyGroup>
  <CaiLoggingMaxDepth>3</CaiLoggingMaxDepth>
  <CaiLoggingMaxItems>20</CaiLoggingMaxItems>
  <CaiLoggingMaxProperties>50</CaiLoggingMaxProperties>
</PropertyGroup>

These are baked into the generated LogOptions class. Arrays and dictionaries are capped at maxItems, complex objects at maxProperties, and a .__truncated flag is emitted when a limit is hit. Depth limits prevent dumping large nested graphs.

ILoggable

Types can take full control of what gets logged by implementing Cai.ILoggable. Return relative dotted keys with primitive leaf values; the generated Logging helper prefixes them with the section being logged (e.g. input.order.id):

public record Add(int value1, int value2) : ILoggable
{
    public IEnumerable<KeyValuePair<string, object?>> getLogData()
    {
        yield return new("value1", value1);
        yield return new("value2", value2);
    }
}

Measures (OpenTelemetry metrics)

Generated end-to-end metrics are exported through the OTLP endpoints configured under otel.metrics (console/Seq). They flow to the telemetry backend, not to NATS; the NATS message counters are generated from the service handlers.

Global measures (always on, unless disabled)

Name Type Unit Attributes
cai.request.active UpDownCounter requests operation, kind, transport, outcome
cai.request.processed Counter requests operation, kind, transport, outcome
cai.request.duration Histogram ms operation, kind, transport, outcome
cai.request.errors Counter requests operation, kind, transport, outcome
cai.nats.messages.active UpDownCounter messages service, subject, outcome
cai.nats.messages.processed Counter messages service, subject, outcome
cai.nats.message.duration Histogram ms service, subject, outcome
cai.nats.messages.errors Counter messages service, subject, outcome
cai.subscriptions.active UpDownCounter streams outcome
cai.subscriptions.processed Counter events outcome
cai.subscriptions.errors Counter events outcome

Per-type measures (only for records marked [Measure])

Apply [Measure] to a record to get dedicated per-type instruments (e.g. "how many Add messages are being processed"). Without the attribute the record only contributes to the global counts; with it, per-type instruments are generated and its endpoints/clients record into both.

[Measure]
[Distributed("calc.add")]
public record Add(int value1, int value2);
  • cai.measure.<type>.active / .processed / .duration / .errors — operations of that type, with a scope attribute (request = generated client, message = NATS server handler) and outcome.
  • cai.subscriptions.<type>.active / .processed / .errors — subscription events, in a separate measure family, when the record is a subscription.

Configurability

  • Runtime switch (appsettings): otel.metricsEnabled (bool, default true). When false, the metrics pipeline is not wired and all instrument calls become near no-ops.

  • Compile-time kill switch (project property, like the logging limits):

    <PropertyGroup>
      <CaiMetricsEnabled>true</CaiMetricsEnabled>
      <CaiMetricsPrefix>cai</CaiMetricsPrefix>
    </PropertyGroup>
    

    When CaiMetricsEnabled is false, no metrics code is emitted at all (zero overhead). CaiMetricsPrefix changes the meter/instrument name prefix (default cai, e.g. cai.request.active), so you can scope names per project (apitest.request.active).

    The cai.analyzers package ships a buildTransitive/cai.analyzers.props that automatically exposes the Cai* properties to the generator (CompilerVisibleProperty), so package consumers only set the property values — no extra items. (ProjectReference-based development setups must add the CompilerVisibleProperty items themselves, since buildTransitive assets are not propagated by ProjectReference.)

  • Custom meters: otel.meters lists additional meter names to subscribe:

    "otel": { "metricsEnabled": true, "meters": ["Cai.Test.Metrics"] }
    

    Define your own instruments in the app and they are exported automatically:

    public static class TestMetrics
    {
        private static readonly Meter meter = new("Cai.Test.Metrics", "1.0.0");
        private static readonly Counter<long> adds = meter.CreateCounter<long>("cai.test.adds");
        public static void addProcessed() => adds.Add(1);
    }
    

    Instruments are thread-safe and need no DI, so any handler/endpoint can increment them: TestMetrics.addProcessed();.

    Do not name an app-level metrics class AppMetrics — it collides with the generated App.Features.Observability.AppMetrics in the App.* namespace chain.

  • In-code customization: addCai(...) accepts a metricsOverrides action (also passed to addTelemetry) for anything otel.meters can't express (views, readers, extra AddMeter, etc.):

    builder.addCai((opts) => { ... }, (gql) => { ... }, metricsOverrides: m =>
    {
        m.AddMeter("Some.Other.Meter");
        // m.AddView(...); etc.
    });
    

Multiple instances (distributed)

Metrics are aggregated per process. Each instance exports its own timeseries tagged with the resource attributes service.name and service.instance.id (set via otel.serviceInstanceId, falling back to the HOSTNAME env var, then a generated id). Aggregate views across instances use sum(...) at query time (e.g. fleet-wide sum(cai_nats_messages_processed)); "active" is per-instance and a global active count is sum(cai_nats_messages_active) over instances.

Distributed tracing span model

A request that travels over NATS produces two spans in one trace (same traceId):

  • the client-side request span (HTTP/GraphQL resolver), and
  • the NATS server-handler span, named after the subject (e.g. calc.add), linked as a child via the activityState header.

If a client span appears named after an arbitrary action (e.g. Add Some Number), it comes from the calling code setting its own action name (session.setAction(...)), not from the generated handler.

NATS service startup

NATS services start through a generated NatsServicesHost (BackgroundService) with exponential backoff (1s → 2s → 4s … cap 30s). It pings the connection first and retries with warnings when NATS is unavailable, so the app starts even if NATS is down and the services register themselves once it is reachable. Errors starting a service are logged via ILogger. The service classes resolve IMessageBus per invocation from a disposed scope (no scope leak), and empty service versions default to 0.0.1.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.30.0 79 10/4/2026
0.22.6 95 9/20/2026
0.22.5 159 6/29/2026
0.22.4 112 6/28/2026
0.22.3 120 6/28/2026
0.22.2 114 6/28/2026
0.22.1 123 5/18/2026
0.22.0 112 5/18/2026
0.21.1 2,446 2/1/2025
0.21.0 209 1/10/2025
0.20.0 189 1/8/2025
0.10.6 323 11/2/2024
0.10.4 387 8/5/2024
0.10.3 318 3/13/2024
0.10.2 393 3/13/2024
0.10.1 222 3/13/2024
0.10.0 431 1/29/2024
0.0.9 293 1/20/2024
0.0.8 201 1/19/2024
0.0.7 221 1/12/2024
Loading failed