Logfire.Fleet.Client 0.2.1

dotnet add package Logfire.Fleet.Client --version 0.2.1
                    
NuGet\Install-Package Logfire.Fleet.Client -Version 0.2.1
                    
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="Logfire.Fleet.Client" Version="0.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Logfire.Fleet.Client" Version="0.2.1" />
                    
Directory.Packages.props
<PackageReference Include="Logfire.Fleet.Client" />
                    
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 Logfire.Fleet.Client --version 0.2.1
                    
#r "nuget: Logfire.Fleet.Client, 0.2.1"
                    
#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 Logfire.Fleet.Client@0.2.1
                    
#: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=Logfire.Fleet.Client&version=0.2.1
                    
Install as a Cake Addin
#tool nuget:?package=Logfire.Fleet.Client&version=0.2.1
                    
Install as a Cake Tool

Logfire.Fleet.Client (.NET)

An OpAMP client for .NET 8 and later, over plain HTTP or WebSocket.

Everything OpAMP (what to send and when, backoff, heartbeats, full reports, instance UIDs, agent_disconnect, and the requests and responses themselves) is the shared core, run natively: a library built from the same source as the WebAssembly module the Go, Node.js and Java clients run, called through LibraryImport, and held to that module byte for byte (NativeCoreTests). By default the core speaks HTTP and WebSocket itself too (core mode), and this library only gives it .NET's sockets and SslStream, so the bytes on the wire are the same as every other language's client here. In platform mode the library carries out the core's requests with HttpClient and ClientWebSocket instead.

To put automatic instrumentation under a server without writing code, use the plugin next to it, Logfire.Fleet.AutoInstrumentation (docs/dotnet-otel.md). An SDK set up in code takes one call, services.AddOpenTelemetry().WithOpAmp(), from Logfire.Fleet.Extensions.Hosting (the same document). To decide what an SDK does with the server's settings by hand, Logfire.Fleet.Client.Sdk.SdkSettings runs the shared SDK settings layer (docs/sdk-settings.md).

var client = OpAmpClient.Start(
    new OpAmpClientOptions
    {
        Endpoint = new Uri("wss://opamp.example.com/v1/opamp"),
        Headers = new Dictionary<string, string> { ["Authorization"] = "Bearer ..." },
    },
    new OpAmpStatus { Description = description },
    (c, n) =>
    {
        if (n is OpAmpNotification.RemoteConfig r)
        {
            // apply r.Config, then c.SetRemoteConfigStatus(...)
        }
    });
// ...
await client.StopAsync(); // sends agent_disconnect, then closes

The transport follows the endpoint's scheme, as the core reads it: http(s):// polls, ws(s):// keeps one connection open. Give the agent's status to Start so the first message reports it in full. Notifications come on the client's loop, never before Start returns, with the client to answer through: a remote config saved before a restart is handed over as the client starts, so answer through the c the callback is given rather than the variable Start assigns.

With no InstanceUid, the core makes one (a UUIDv7, from the clock and 32 bytes of the operating system's randomness) and, with a state file, keeps it across restarts. client.InstanceUid says which UID is in use.

The client keeps no time of its own. How long a request, a WebSocket connection being opened, a closing handshake, a download and stop may take is the core's (RequestTimeout, ConnectTimeout, StopTimeout, DownloadTimeout): when one runs out, the core cancels the effect and the client aborts it at once, and reports nothing more about it. StopAsync ends when the core says it has stopped (within StopTimeout, 5 s by default, after waiting for the goodbye and the closing handshake); then everything still open is closed, and work the core cancelled is let finish within that time again. A save already writing at stop finishes, whole or not at all, and a client started on the same state file waits for it.

Package downloads follow no redirect. Whatever the status, the body goes to the file, hashed, and the core is told the status and, for a redirect, the Location resolved with new Uri(requested, location) and its origin: the core decides whether to follow it.

Set OPAMP_CORE_TAP=<path> to record every call into the core (for opamp-tap): the first core in the process writes <path>, each later one <path>.<pid>.<n>. A tap holds the headers sent (the enrollment key) and the credentials saved, so a new one is the user's only.

The native core comes in its own package, Logfire.Fleet.Core.Native, which this one depends on: the library alone for Linux (glibc and musl, so Alpine too), macOS and Windows, on x64 and arm64 (runtimes/<rid>/native), about 2 MB each, with no assembly and no source. This package, Logfire.Fleet.Client, holds only the client's assembly.

TLS and proxies

Tls sets the CAs to trust, a client certificate and the TLS versions. Proxy sets a proxy. Both apply to every connection the client makes, in both modes: requests, the WebSocket and package downloads.

var options = new OpAmpClientOptions
{
    Endpoint = new Uri("wss://opamp.example.com/v1/opamp"),
    Tls = new OpAmpTlsOptions
    {
        CaPem = File.ReadAllText("ca.pem"),
        ClientCertPem = File.ReadAllText("agent.pem"),
        ClientKeyPem = File.ReadAllText("agent-key.pem"),
    },
    Proxy = new Uri("http://user:password@proxy.example.com:3128"),
};
  • CaPem holds one or more CA certificates. They replace the system's CAs. Set IncludeSystemCas to trust both. Without CaPem the system's CAs are used.
  • The server's certificate is always checked, and so is its name (SNI unless the host is an address). There is no way to turn that off.
  • ClientCertPem and ClientKeyPem go together. The key is PKCS #8, PKCS #1 or SEC 1, not encrypted. Certificates after the first in ClientCertPem are sent as its chain.
  • The client uses TLS 1.2 and 1.3. MinVersion and MaxVersion narrow that to one of them. Cipher suites can't be chosen: .NET doesn't support that on Windows.
  • Proxy is http://, https://, socks5:// or socks5h://. Credentials go in the URL, percent-encoded. An HTTP proxy always gets a CONNECT tunnel, plain http:// included. The client can't add headers of its own to the CONNECT request. The TLS settings apply to an https:// proxy too.
  • A SOCKS5 proxy is given the server's host name and resolves it, with socks5:// and socks5h:// alike. Unlike curl, where socks5:// resolves the name locally. ProxyResolveLocally has the client resolve the name instead and give the proxy an address; with an HTTP proxy it is an error.
  • socks4:// and socks4a:// are refused, and so is any other scheme: a proxy the client can't use is an error, never a direct connection.
  • Without Proxy the proxy comes from the environment by the rule every SDK's core mode follows: tests/shell-tables/README.md, its proxy rule (put in place by scripts/fetch_core.py fetch) (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY and NO_PROXY, the upper-case name first; NO_PROXY names, addresses and CIDR networks; nothing under CGI). ProxyResolveLocally then applies to a SOCKS5 proxy the environment names. It does not read the system's proxy settings on Windows and macOS, nor HttpClient.DefaultProxy: those are platform mode's. Set DirectConnection to go direct instead. It can't be combined with Proxy.
  • Package downloads use the same settings. With CaPem set, a download from another server (where a redirect points, say) needs that server's CA in CaPem, or IncludeSystemCas.
  • Bad settings throw ArgumentException from Start.

.NET checks the server's certificate once the TLS handshake is done, not during it. A server the client can't verify sees the handshake complete, then the connection close. The client sends it nothing: no request, no upgrade, not a byte of application data. The conformance suite checks both: rejects_untrusted_server passes, and refuses_in_handshake (a SHOULD) warns.

Core mode and platform mode

Mode says who speaks HTTP and WebSocket. The client runs in core mode unless the application asks for platform mode: by naming it in the mode option, or by handing the client an HTTP client of its own, where the SDK takes one (an application that brings its own client has asked for the platform's stack). An option only one mode honours is never silently ignored: it selects that mode, or the client refuses to start with an error that says what to set instead.

Here platform mode is Mode = OpAmpMode.Platform, and setting HttpClient selects it too. Start throws for Mode = OpAmpMode.Core with an HttpClient, and in platform mode for ProxyResolveLocally and SpliceDownloads = false.

In core mode (Mode = OpAmpMode.Core, or no Mode) the shared core speaks HTTP/1.1, WebSocket, CONNECT and SOCKS5 itself (in the same native library). .NET then only opens TCP connections (Socket, TCP_NODELAY), starts TLS on them (SslStream, TLS inside TLS for an https:// proxy), resolves names when asked to, answers which proxy to use, and writes package files; every byte on the wire is the core's.

  • SpliceDownloads (default true): package bodies of 16 KiB or more go from the socket to the file without passing through the core.
  • A connection the core closes keeps 2 s at most to flush what was written and hear the server end it; StopAsync resets whatever is still open, so it still ends within StopTimeout.

Choose platform mode to pass your own HttpClient, or when the connection must be made by .NET's HTTP stack: with the system's proxy settings, say.

var options = new OpAmpClientOptions
{
    Endpoint = new Uri("https://opamp.example.com/v1/opamp"),
    Mode = OpAmpMode.Platform,
};

What differs there:

  • Without Proxy the client uses the system's proxy settings (HttpClient.DefaultProxy: HTTPS_PROXY and the like, and on Windows and macOS the system's own), for requests, the WebSocket and downloads alike. DirectConnection goes direct instead.
  • The environment's proxy URLs (http_proxy, https_proxy, all_proxy, in either case) are held to the same schemes, as is a WebProxy the application sets as HttpClient.DefaultProxy. One exception: .NET doesn't read socks5h:// there (it would connect directly), so it is an error too. Write it as socks5://, which has the proxy resolve names just the same, or set Proxy. The system's own proxy settings on Windows and macOS aren't checked.
  • An HTTP proxy gets plain http:// requests in absolute form and a CONNECT tunnel for everything else (https://, ws://, wss://).
  • ProxyResolveLocally is an error: .NET's SOCKS5 always sends the name, and an HTTP proxy is always given it.
  • You can pass your own HttpClient. It then carries everything, the WebSocket and downloads included, so set TLS and the proxy on its handler. It can't be combined with Tls, Proxy or DirectConnection.

The state file

With a state file (StateFile) the client keeps what it needs across restarts: accepted connection settings, the instance UID, the last remote config, telemetry settings and packages. It holds secrets: the credentials of connection settings the server offered (bearer tokens in Authorization headers) and the private keys of telemetry settings' client certificates. The client writes it for the agent's user alone: mode 0600 on Linux and macOS, and on Windows an access-control list that allows only that user, with nothing inherited from the directory. Package files downloaded beside it (<state file>.downloads, named for the client that downloads them) are protected the same way. Saves run beside the client's loop, one at a time. Keep it on a local disk, in a directory only the agent's user can read: not on a shared or network path, and not in backups or images copied anywhere less protected.

Development

../scripts/fetch_core.py fetch             # first: the core's release (the native library in target/release, the modules)
dotnet test --project Logfire.Fleet.Client.Tests   # needs the .NET 10 SDK, and the .NET 8 runtime: net8.0 and net10.0
../scripts/gen_core.py                      # after the core's .proto files change: regenerates the types
../scripts/native_dist.py                   # the package, with every platform's native core (CI's native job)

The conformance suite runs this client through conformance/drivers/dotnet-core, in both modes: dotnet-core (core mode) and dotnet-core-platform (platform mode, OPAMP_DOTNET_MODE=platform). The tests run core mode over an in-memory network (MemNet, with MemServer at its far end) as well as real sockets.

What the client promises around its callback (when it is called, one at a time and never before start returns, what a callback that throws does, which calls are allowed from the callback and from other threads, stop from the callback) is the SDK contract, the same for every SDK here. The precise rules, around the core too, are the shell contract, spec/shell-contract.md (put in place by scripts/fetch_core.py fetch; it lists the breaks known today). The callback model test and the adversary harness in this client's tests check it.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Logfire.Fleet.Client:

Package Downloads
Logfire.Fleet.AutoInstrumentation

A plugin for OpenTelemetry .NET automatic instrumentation: the SDK under an OpAMP server

Logfire.Fleet.Extensions.Hosting

An OpenTelemetry .NET SDK set up in code (services.AddOpenTelemetry()) under an OpAMP server, in one call: .WithOpAmp()

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.1 108 11/6/2026
0.2.0 255 11/5/2026