Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/package-library-metadata.md
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 11 additions & 0 deletions src/PostHog.AI/Generated/VersionConstants.cs
Original file line number Diff line number Diff line change
@@ -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";
}
6 changes: 6 additions & 0 deletions src/PostHog.AI/PostHog.AI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@
<None Include="README.md" Pack="true" PackagePath="\"/>
<None Include="../package-icon.png" Link="package-icon.png" Pack="true" PackagePath="\package-icon.png" />
</ItemGroup>
<Target Name="GenerateVersionConstants" BeforeTargets="BeforeBuild">
<WriteLinesToFile
File="Generated/VersionConstants.cs"
Lines="/*&#10; GENERATED FILE! Do not directly modify this file.&#10; This is generated by an MSBuild task in PostHog.AI.csproj&#10;*/&#10;&#10;namespace PostHog.AI.Versioning%3B&#10;&#10;internal static class VersionConstants&#10;{&#10; public const string Version = &quot;$(Version)&quot;%3B&#10;}&#10;"
Overwrite="true" />
</Target>
</Project>
7 changes: 6 additions & 1 deletion src/PostHog.AI/PostHogAIFieldNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@ public static class PostHogAIFieldNames
public const string Provider = "$ai_provider";

/// <summary>
/// Library identifier (e.g., "posthog-dotnet").
/// AI library identifier (e.g., "posthog-ai").
/// </summary>
public const string Lib = "$ai_lib";

/// <summary>
/// AI library version.
/// </summary>
public const string LibVersion = "$ai_lib_version";

/// <summary>
/// Request latency in seconds.
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion src/PostHog.AI/PostHogOpenAIHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/PostHog.AI/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
const PostHog.AI.PostHogAIFieldNames.LibVersion = "$ai_lib_version" -> string!
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public static IPostHogConfigurationBuilder UseAspNetCore(this IPostHogConfigurat
{
services.AddSingleton<IFeatureFlagCache, HttpContextFeatureFlagCache>();
services.AddHttpContextAccessor();
services.PostConfigure<PostHogOptions>(options =>
{
options.LibraryName = "posthog-aspnetcore";
options.LibraryVersion = global::PostHog.AspNetCore.Versioning.VersionConstants.Version;
});
});
return builder;
}
Expand Down
11 changes: 11 additions & 0 deletions src/PostHog.AspNetCore/Generated/VersionConstants.cs
Original file line number Diff line number Diff line change
@@ -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";
}
7 changes: 7 additions & 0 deletions src/PostHog.AspNetCore/PostHog.AspNetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,11 @@
<ProjectReference Include="..\PostHog\PostHog.csproj" />
</ItemGroup>

<Target Name="GenerateVersionConstants" BeforeTargets="BeforeBuild">
<WriteLinesToFile
File="Generated/VersionConstants.cs"
Lines="/*&#10; GENERATED FILE! Do not directly modify this file.&#10; This is generated by an MSBuild task in PostHog.AspNetCore.csproj&#10;*/&#10;&#10;namespace PostHog.AspNetCore.Versioning%3B&#10;&#10;internal static class VersionConstants&#10;{&#10; public const string Version = &quot;$(Version)&quot;%3B&#10;}&#10;"
Overwrite="true" />
</Target>

</Project>
21 changes: 19 additions & 2 deletions src/PostHog/Api/CapturedEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ public CapturedEvent(
string distinctId,
Dictionary<string, object>? properties,
DateTimeOffset timestamp)
: this(
eventName,
distinctId,
properties,
timestamp,
PostHogApiClient.LibraryName,
VersionConstants.Version)
Comment thread
marandaneto marked this conversation as resolved.
{
}

internal CapturedEvent(
string eventName,
string distinctId,
Dictionary<string, object>? properties,
DateTimeOffset timestamp,
string libraryName,
string libraryVersion)
{
Uuid = Guid.NewGuid().ToString();
EventName = eventName;
Expand All @@ -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);
}

Expand Down
8 changes: 4 additions & 4 deletions src/PostHog/Api/PostHogApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using Microsoft.Extensions.Options;
using PostHog.Json;
using PostHog.Library;
using PostHog.Versioning;
#if NETSTANDARD2_0 || NETSTANDARD2_1
#endif

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -286,8 +286,8 @@ void PrepareAndMutatePayload(Dictionary<string, object> payload)

var properties = payload.GetOrAdd<string, Dictionary<string, object>>("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();
Expand Down
5 changes: 5 additions & 0 deletions src/PostHog/Config/PostHogOptions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Options;
using PostHog.Api;
using PostHog.Library;
using PostHog.Versioning;

namespace PostHog;

Expand All @@ -14,6 +15,10 @@ public sealed class PostHogOptions : IOptions<PostHogOptions>
string? _secretKey;
string? _personalApiKey;

internal string LibraryName { get; set; } = PostHogApiClient.LibraryName;

internal string LibraryVersion { get; set; } = VersionConstants.Version;

/// <summary>
/// The PostHog project token that identifies which project this client works with.
/// </summary>
Expand Down
10 changes: 7 additions & 3 deletions src/PostHog/PostHogClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
{
Expand Down Expand Up @@ -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<string, object>(MinimalFeatureFlagCalledEventProperties.Length);
foreach (var key in MinimalFeatureFlagCalledEventProperties)
Expand All @@ -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<CapturedEvent> batch)
Expand Down
11 changes: 11 additions & 0 deletions tests/PostHog.AI.Tests/PostHogOpenAIHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,17 @@ private static bool VerifyProps(Dictionary<string, object> 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;
}

Expand Down
40 changes: 40 additions & 0 deletions tests/UnitTests.AspNetCore/RegistrationTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -42,6 +43,45 @@ public void ReadsSettingsFromPostHogConfigurationSection()
Assert.Equal(TimeSpan.FromSeconds(10), options.FeatureFlagPollInterval);
}

[Fact]
public async Task UsesAspNetCoreLibraryMetadata()
{
var builder = WebApplication.CreateSlimBuilder();
using var messageHandler = new FakeHttpMessageHandler();
var flagsRequestHandler = messageHandler.AddFlagsResponse("""{"featureFlags": {}}""");
var batchRequestHandler = messageHandler.AddBatchResponse();
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
["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<IPostHogClient>();
await client.GetAllFeatureFlagsAsync("test-user", null, CancellationToken.None);
client.Capture("test-user", "test-event");
await client.FlushAsync();

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]
public void DoesNotLogLegacyWarningWhenOnlyProjectTokenIsConfigured()
{
Expand Down
Loading