From 02a246bf7bfdaddf24c565a544935ab1467a0ac0 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Thu, 3 Sep 2026 09:46:43 +0200 Subject: [PATCH 1/2] fix: report package-specific library metadata --- .changeset/package-library-metadata.md | 7 +++++ src/PostHog.AI/Generated/VersionConstants.cs | 11 +++++++ src/PostHog.AI/PostHog.AI.csproj | 6 ++++ src/PostHog.AI/PostHogAIFieldNames.cs | 7 ++++- src/PostHog.AI/PostHogOpenAIHandler.cs | 3 +- src/PostHog.AI/PublicAPI.Unshipped.txt | 1 + .../PostHogConfigurationBuilderExtensions.cs | 5 ++++ .../Generated/VersionConstants.cs | 11 +++++++ .../PostHog.AspNetCore.csproj | 7 +++++ src/PostHog/Api/CapturedEvent.cs | 21 ++++++++++++-- src/PostHog/Api/PostHogApiClient.cs | 8 ++--- src/PostHog/Config/PostHogOptions.cs | 5 ++++ src/PostHog/PostHogClient.cs | 10 +++++-- .../PostHogOpenAIHandlerTests.cs | 11 +++++++ .../UnitTests.AspNetCore/RegistrationTests.cs | 29 +++++++++++++++++++ 15 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 .changeset/package-library-metadata.md create mode 100644 src/PostHog.AI/Generated/VersionConstants.cs create mode 100644 src/PostHog.AspNetCore/Generated/VersionConstants.cs diff --git a/.changeset/package-library-metadata.md b/.changeset/package-library-metadata.md new file mode 100644 index 00000000..651b57bc --- /dev/null +++ b/.changeset/package-library-metadata.md @@ -0,0 +1,7 @@ +--- +"PostHog": patch +"PostHog.AspNetCore": patch +"PostHog.AI": patch +--- + +Report package-specific library names and versions for ASP.NET Core and AI events. diff --git a/src/PostHog.AI/Generated/VersionConstants.cs b/src/PostHog.AI/Generated/VersionConstants.cs new file mode 100644 index 00000000..aba5e73c --- /dev/null +++ b/src/PostHog.AI/Generated/VersionConstants.cs @@ -0,0 +1,11 @@ +/* + GENERATED FILE! Do not directly modify this file. + This is generated by an MSBuild task in PostHog.AI.csproj +*/ + +namespace PostHog.AI.Versioning; + +internal static class VersionConstants +{ + public const string Version = "0.1.4"; +} diff --git a/src/PostHog.AI/PostHog.AI.csproj b/src/PostHog.AI/PostHog.AI.csproj index c16ba848..8e35ad44 100644 --- a/src/PostHog.AI/PostHog.AI.csproj +++ b/src/PostHog.AI/PostHog.AI.csproj @@ -21,4 +21,10 @@ + + + diff --git a/src/PostHog.AI/PostHogAIFieldNames.cs b/src/PostHog.AI/PostHogAIFieldNames.cs index a26e64c7..4c2f96f1 100644 --- a/src/PostHog.AI/PostHogAIFieldNames.cs +++ b/src/PostHog.AI/PostHogAIFieldNames.cs @@ -24,10 +24,15 @@ public static class PostHogAIFieldNames public const string Provider = "$ai_provider"; /// - /// Library identifier (e.g., "posthog-dotnet"). + /// AI library identifier (e.g., "posthog-ai"). /// public const string Lib = "$ai_lib"; + /// + /// AI library version. + /// + public const string LibVersion = "$ai_lib_version"; + /// /// Request latency in seconds. /// diff --git a/src/PostHog.AI/PostHogOpenAIHandler.cs b/src/PostHog.AI/PostHogOpenAIHandler.cs index 3716ccc8..f1bfec7b 100644 --- a/src/PostHog.AI/PostHogOpenAIHandler.cs +++ b/src/PostHog.AI/PostHogOpenAIHandler.cs @@ -239,7 +239,8 @@ private void CaptureEvent( // Basic Info eventProperties[PostHogAIFieldNames.Provider] = "openai"; - eventProperties[PostHogAIFieldNames.Lib] = "posthog-dotnet"; + eventProperties[PostHogAIFieldNames.Lib] = "posthog-ai"; + eventProperties[PostHogAIFieldNames.LibVersion] = Versioning.VersionConstants.Version; eventProperties[PostHogAIFieldNames.Latency] = latency; if (request.RequestUri != null) diff --git a/src/PostHog.AI/PublicAPI.Unshipped.txt b/src/PostHog.AI/PublicAPI.Unshipped.txt index 7dc5c581..255a43e4 100644 --- a/src/PostHog.AI/PublicAPI.Unshipped.txt +++ b/src/PostHog.AI/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +const PostHog.AI.PostHogAIFieldNames.LibVersion = "$ai_lib_version" -> string! diff --git a/src/PostHog.AspNetCore/Config/PostHogConfigurationBuilderExtensions.cs b/src/PostHog.AspNetCore/Config/PostHogConfigurationBuilderExtensions.cs index dd4bf635..364b4375 100644 --- a/src/PostHog.AspNetCore/Config/PostHogConfigurationBuilderExtensions.cs +++ b/src/PostHog.AspNetCore/Config/PostHogConfigurationBuilderExtensions.cs @@ -25,6 +25,11 @@ public static IPostHogConfigurationBuilder UseAspNetCore(this IPostHogConfigurat { services.AddSingleton(); services.AddHttpContextAccessor(); + services.PostConfigure(options => + { + options.LibraryName = "posthog-aspnetcore"; + options.LibraryVersion = global::PostHog.AspNetCore.Versioning.VersionConstants.Version; + }); }); return builder; } diff --git a/src/PostHog.AspNetCore/Generated/VersionConstants.cs b/src/PostHog.AspNetCore/Generated/VersionConstants.cs new file mode 100644 index 00000000..b8f353ca --- /dev/null +++ b/src/PostHog.AspNetCore/Generated/VersionConstants.cs @@ -0,0 +1,11 @@ +/* + GENERATED FILE! Do not directly modify this file. + This is generated by an MSBuild task in PostHog.AspNetCore.csproj +*/ + +namespace PostHog.AspNetCore.Versioning; + +internal static class VersionConstants +{ + public const string Version = "2.9.5"; +} diff --git a/src/PostHog.AspNetCore/PostHog.AspNetCore.csproj b/src/PostHog.AspNetCore/PostHog.AspNetCore.csproj index a0c87f7f..261d7128 100644 --- a/src/PostHog.AspNetCore/PostHog.AspNetCore.csproj +++ b/src/PostHog.AspNetCore/PostHog.AspNetCore.csproj @@ -22,4 +22,11 @@ + + + + diff --git a/src/PostHog/Api/CapturedEvent.cs b/src/PostHog/Api/CapturedEvent.cs index a160cf7f..e2beef97 100644 --- a/src/PostHog/Api/CapturedEvent.cs +++ b/src/PostHog/Api/CapturedEvent.cs @@ -21,6 +21,23 @@ public CapturedEvent( string distinctId, Dictionary? properties, DateTimeOffset timestamp) + : this( + eventName, + distinctId, + properties, + timestamp, + PostHogApiClient.LibraryName, + VersionConstants.Version) + { + } + + internal CapturedEvent( + string eventName, + string distinctId, + Dictionary? properties, + DateTimeOffset timestamp, + string libraryName, + string libraryVersion) { Uuid = Guid.NewGuid().ToString(); EventName = eventName; @@ -31,8 +48,8 @@ public CapturedEvent( // Every event has to have these properties. Properties[PostHogProperties.DistinctId] = distinctId; // See `get_distinct_id` in PostHog/posthog api/capture.py line 321 - Properties[PostHogProperties.Lib] = PostHogApiClient.LibraryName; - Properties[PostHogProperties.LibVersion] = VersionConstants.Version; + Properties[PostHogProperties.Lib] = libraryName; + Properties[PostHogProperties.LibVersion] = libraryVersion; Properties[PostHogProperties.GeoIpDisable] = Properties.GetValueOrDefault(PostHogProperties.GeoIpDisable, true); } diff --git a/src/PostHog/Api/PostHogApiClient.cs b/src/PostHog/Api/PostHogApiClient.cs index ff5777b0..bf14b396 100644 --- a/src/PostHog/Api/PostHogApiClient.cs +++ b/src/PostHog/Api/PostHogApiClient.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.Options; using PostHog.Json; using PostHog.Library; -using PostHog.Versioning; #if NETSTANDARD2_0 || NETSTANDARD2_1 #endif @@ -46,7 +45,8 @@ public PostHogApiClient( var framework = RuntimeInformation.FrameworkDescription; var os = RuntimeInformation.OSDescription; var arch = RuntimeInformation.ProcessArchitecture; - _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd($"{LibraryName}/{VersionConstants.Version} ({framework}; {os}; {arch})"); + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd( + $"{_options.Value.LibraryName}/{_options.Value.LibraryVersion} ({framework}; {os}; {arch})"); logger.LogTraceApiClientCreated(HostUrl); _logger = logger; @@ -286,8 +286,8 @@ void PrepareAndMutatePayload(Dictionary payload) var properties = payload.GetOrAdd>("properties"); - properties[PostHogProperties.Lib] = LibraryName; - properties[PostHogProperties.LibVersion] = VersionConstants.Version; + properties[PostHogProperties.Lib] = _options.Value.LibraryName; + properties[PostHogProperties.LibVersion] = _options.Value.LibraryVersion; properties[PostHogProperties.Os] = RuntimeInformation.OSDescription; properties[PostHogProperties.Framework] = RuntimeInformation.FrameworkDescription; properties[PostHogProperties.Architecture] = RuntimeInformation.ProcessArchitecture.ToString(); diff --git a/src/PostHog/Config/PostHogOptions.cs b/src/PostHog/Config/PostHogOptions.cs index 70b0cc1f..6dee122c 100644 --- a/src/PostHog/Config/PostHogOptions.cs +++ b/src/PostHog/Config/PostHogOptions.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Options; using PostHog.Api; using PostHog.Library; +using PostHog.Versioning; namespace PostHog; @@ -14,6 +15,10 @@ public sealed class PostHogOptions : IOptions string? _secretKey; string? _personalApiKey; + internal string LibraryName { get; set; } = PostHogApiClient.LibraryName; + + internal string LibraryVersion { get; set; } = VersionConstants.Version; + /// /// The PostHog project token that identifies which project this client works with. /// diff --git a/src/PostHog/PostHogClient.cs b/src/PostHog/PostHogClient.cs index 70a87f15..5d059a5b 100644 --- a/src/PostHog/PostHogClient.cs +++ b/src/PostHog/PostHogClient.cs @@ -333,7 +333,9 @@ bool CaptureCore( eventName, captureContext.DistinctId, captureContext.Properties, - timestamp: timestamp ?? _timeProvider.GetUtcNow()); + timestamp: timestamp ?? _timeProvider.GetUtcNow(), + _options.Value.LibraryName, + _options.Value.LibraryVersion); if (groups is { Count: > 0 }) { @@ -407,7 +409,7 @@ static bool ShouldSendMinimalFeatureFlagCalledEvent(bool gate, bool? hasExperime // set, so anything merged upstream (super properties, context properties) cannot leak in. The // CapturedEvent constructor re-adds distinct_id and defaults $geoip_disable to true when it // was not explicitly set. - static CapturedEvent ToMinimalFeatureFlagCalledEvent(CapturedEvent capturedEvent) + CapturedEvent ToMinimalFeatureFlagCalledEvent(CapturedEvent capturedEvent) { var properties = new Dictionary(MinimalFeatureFlagCalledEventProperties.Length); foreach (var key in MinimalFeatureFlagCalledEventProperties) @@ -422,7 +424,9 @@ static CapturedEvent ToMinimalFeatureFlagCalledEvent(CapturedEvent capturedEvent capturedEvent.EventName, capturedEvent.DistinctId, properties, - capturedEvent.Timestamp); + capturedEvent.Timestamp, + _options.Value.LibraryName, + _options.Value.LibraryVersion); } async Task CaptureBatchAsync(IEnumerable batch) diff --git a/tests/PostHog.AI.Tests/PostHogOpenAIHandlerTests.cs b/tests/PostHog.AI.Tests/PostHogOpenAIHandlerTests.cs index 587c658d..b88d2699 100644 --- a/tests/PostHog.AI.Tests/PostHogOpenAIHandlerTests.cs +++ b/tests/PostHog.AI.Tests/PostHogOpenAIHandlerTests.cs @@ -309,6 +309,17 @@ private static bool VerifyProps(Dictionary props) || (string)provider != "openai" ) return false; + if ( + !props.TryGetValue(PostHogAIFieldNames.Lib, out var library) + || (string)library != "posthog-ai" + ) + return false; + if ( + !props.TryGetValue(PostHogAIFieldNames.LibVersion, out var libraryVersion) + || (string)libraryVersion + != typeof(PostHogOpenAIHandler).Assembly.GetName().Version?.ToString(3) + ) + return false; return true; } diff --git a/tests/UnitTests.AspNetCore/RegistrationTests.cs b/tests/UnitTests.AspNetCore/RegistrationTests.cs index c0226299..1689a68e 100644 --- a/tests/UnitTests.AspNetCore/RegistrationTests.cs +++ b/tests/UnitTests.AspNetCore/RegistrationTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Text.Json; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -42,6 +43,34 @@ public void ReadsSettingsFromPostHogConfigurationSection() Assert.Equal(TimeSpan.FromSeconds(10), options.FeatureFlagPollInterval); } + [Fact] + public async Task CapturesWithAspNetCoreLibraryMetadata() + { + var builder = WebApplication.CreateSlimBuilder(); + using var messageHandler = new FakeHttpMessageHandler(); + var requestHandler = messageHandler.AddBatchResponse(); + builder.Configuration.AddInMemoryCollection(new Dictionary + { + ["PostHog:ProjectToken"] = "fake-not-so-secret", + ["PostHog:EnableCompression"] = "false", + }); + builder.AddPostHog(options => + options.ConfigureHttpClient(httpClient => + httpClient.ConfigurePrimaryHttpMessageHandler(() => messageHandler))); + + await using var provider = builder.Services.BuildServiceProvider(); + var client = provider.GetRequiredService(); + client.Capture("test-user", "test-event"); + await client.FlushAsync(); + + using var document = JsonDocument.Parse(requestHandler.GetReceivedRequestBody(indented: false)); + var properties = document.RootElement.GetProperty("batch")[0].GetProperty("properties"); + Assert.Equal("posthog-aspnetcore", properties.GetProperty("$lib").GetString()); + Assert.Equal( + typeof(Registration).Assembly.GetName().Version?.ToString(3), + properties.GetProperty("$lib_version").GetString()); + } + [Fact] public void DoesNotLogLegacyWarningWhenOnlyProjectTokenIsConfigured() { From 8cb2c60d87e8761eede02731deb016ad1dbb85a4 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Thu, 3 Sep 2026 12:10:48 +0200 Subject: [PATCH 2/2] test: verify aspnetcore request metadata --- .../UnitTests.AspNetCore/RegistrationTests.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/tests/UnitTests.AspNetCore/RegistrationTests.cs b/tests/UnitTests.AspNetCore/RegistrationTests.cs index 1689a68e..ea3ef6ea 100644 --- a/tests/UnitTests.AspNetCore/RegistrationTests.cs +++ b/tests/UnitTests.AspNetCore/RegistrationTests.cs @@ -44,11 +44,12 @@ public void ReadsSettingsFromPostHogConfigurationSection() } [Fact] - public async Task CapturesWithAspNetCoreLibraryMetadata() + public async Task UsesAspNetCoreLibraryMetadata() { var builder = WebApplication.CreateSlimBuilder(); using var messageHandler = new FakeHttpMessageHandler(); - var requestHandler = messageHandler.AddBatchResponse(); + var flagsRequestHandler = messageHandler.AddFlagsResponse("""{"featureFlags": {}}"""); + var batchRequestHandler = messageHandler.AddBatchResponse(); builder.Configuration.AddInMemoryCollection(new Dictionary { ["PostHog:ProjectToken"] = "fake-not-so-secret", @@ -60,15 +61,25 @@ public async Task CapturesWithAspNetCoreLibraryMetadata() await using var provider = builder.Services.BuildServiceProvider(); var client = provider.GetRequiredService(); + await client.GetAllFeatureFlagsAsync("test-user", null, CancellationToken.None); client.Capture("test-user", "test-event"); await client.FlushAsync(); - using var document = JsonDocument.Parse(requestHandler.GetReceivedRequestBody(indented: false)); - var properties = document.RootElement.GetProperty("batch")[0].GetProperty("properties"); - Assert.Equal("posthog-aspnetcore", properties.GetProperty("$lib").GetString()); - Assert.Equal( - typeof(Registration).Assembly.GetName().Version?.ToString(3), - properties.GetProperty("$lib_version").GetString()); + var expectedVersion = typeof(Registration).Assembly.GetName().Version?.ToString(3); + using var flagsDocument = JsonDocument.Parse(flagsRequestHandler.GetReceivedRequestBody(indented: false)); + var flagsProperties = flagsDocument.RootElement.GetProperty("properties"); + Assert.Equal("posthog-aspnetcore", flagsProperties.GetProperty("$lib").GetString()); + Assert.Equal(expectedVersion, flagsProperties.GetProperty("$lib_version").GetString()); + + var userAgent = flagsRequestHandler.ReceivedRequest.Headers.UserAgent; + var userAgentProduct = Assert.Single(userAgent, value => value.Product is not null).Product; + Assert.Equal("posthog-aspnetcore", userAgentProduct?.Name); + Assert.Equal(expectedVersion, userAgentProduct?.Version); + + using var batchDocument = JsonDocument.Parse(batchRequestHandler.GetReceivedRequestBody(indented: false)); + var eventProperties = batchDocument.RootElement.GetProperty("batch")[0].GetProperty("properties"); + Assert.Equal("posthog-aspnetcore", eventProperties.GetProperty("$lib").GetString()); + Assert.Equal(expectedVersion, eventProperties.GetProperty("$lib_version").GetString()); } [Fact]