diff --git a/src/PostHog/Config/PostHogOptions.cs b/src/PostHog/Config/PostHogOptions.cs
index 25f2550..92819a4 100644
--- a/src/PostHog/Config/PostHogOptions.cs
+++ b/src/PostHog/Config/PostHogOptions.cs
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Options;
+using PostHog.Library;
namespace PostHog;
@@ -26,6 +27,15 @@ public string? ProjectToken
internal bool HasLegacyProjectApiKey => _projectApiKey is not null;
+ internal void Normalize()
+ {
+ _projectToken = _projectToken.NullIfEmpty();
+ _projectApiKey = _projectApiKey.NullIfEmpty();
+ PersonalApiKey = PersonalApiKey.NullIfEmpty();
+ HostUrl = HostUrl.NormalizeHostUrl();
+ Disabled = Disabled || ProjectToken is null;
+ }
+
///
/// Obsolete alias for .
///
@@ -49,6 +59,11 @@ public string? ProjectApiKey
///
public string? PersonalApiKey { get; set; }
+ ///
+ /// Whether this client is disabled and should no-op instead of sending data to PostHog. (Default: false)
+ ///
+ public bool Disabled { get; set; }
+
///
/// PostHog API host, usually 'https://us.i.posthog.com' (default) or 'https://eu.i.posthog.com'
///
diff --git a/src/PostHog/PostHogClient.cs b/src/PostHog/PostHogClient.cs
index 564cc11..d0d2299 100644
--- a/src/PostHog/PostHogClient.cs
+++ b/src/PostHog/PostHogClient.cs
@@ -95,16 +95,42 @@ static void NormalizeOptions(PostHogOptions options, ILogger logg
logger.LogWarningProjectApiKeyDeprecated();
}
- options.ProjectToken = options.ProjectToken?.Trim();
- options.PersonalApiKey = options.PersonalApiKey.NullIfEmpty();
- options.HostUrl = options.HostUrl.NormalizeHostUrl();
+ options.Normalize();
- if (string.IsNullOrEmpty(options.ProjectToken))
+ if (options.ProjectToken is null)
{
logger.LogErrorProjectTokenRequired();
}
}
+ bool IsDisabled(string methodName)
+ {
+ if (!_options.Value.Disabled)
+ {
+ return false;
+ }
+
+ _logger.LogWarningClientDisabled(methodName);
+ return true;
+ }
+
+ // A personal_api_key is only required for feature flag calls when callers explicitly request
+ // local-only evaluation. Without it we cannot download local flag definitions, and the
+ // local-only option means we must not fall back to remote /flags evaluation.
+ bool RequiresMissingPersonalApiKey(AllFeatureFlagsOptions? options, string methodName)
+ => options is { OnlyEvaluateLocally: true } && IsPersonalApiKeyMissing(methodName);
+
+ bool IsPersonalApiKeyMissing(string methodName)
+ {
+ if (_options.Value.PersonalApiKey is not null)
+ {
+ return false;
+ }
+
+ _logger.LogWarningPersonalApiKeyMissing(methodName);
+ return true;
+ }
+
///
/// To marry up whatever a user does before they sign up or log in with what they do after you need to make an
/// alias call. This will allow you to answer questions like "Which marketing channels leads to users churning
@@ -122,7 +148,14 @@ public async Task AliasAsync(
string previousId,
string newId,
CancellationToken cancellationToken)
- => await _apiClient.AliasAsync(previousId, newId, cancellationToken);
+ {
+ if (IsDisabled(nameof(AliasAsync)))
+ {
+ return new ApiResult(0);
+ }
+
+ return await _apiClient.AliasAsync(previousId, newId, cancellationToken);
+ }
///
public async Task IdentifyAsync(
@@ -130,11 +163,18 @@ public async Task IdentifyAsync(
Dictionary? personPropertiesToSet,
Dictionary? personPropertiesToSetOnce,
CancellationToken cancellationToken)
- => await _apiClient.IdentifyAsync(
+ {
+ if (IsDisabled(nameof(IdentifyAsync)))
+ {
+ return new ApiResult(0);
+ }
+
+ return await _apiClient.IdentifyAsync(
distinctId,
personPropertiesToSet,
personPropertiesToSetOnce,
cancellationToken);
+ }
///
public Task GroupIdentifyAsync(
@@ -142,7 +182,14 @@ public Task GroupIdentifyAsync(
StringOrValue key,
Dictionary? properties,
CancellationToken cancellationToken)
- => _apiClient.GroupIdentifyAsync(type, key, properties, cancellationToken);
+ {
+ if (IsDisabled(nameof(GroupIdentifyAsync)))
+ {
+ return Task.FromResult(new ApiResult(0));
+ }
+
+ return _apiClient.GroupIdentifyAsync(type, key, properties, cancellationToken);
+ }
///
public Task GroupIdentifyAsync(
@@ -151,7 +198,14 @@ public Task GroupIdentifyAsync(
StringOrValue key,
Dictionary? properties,
CancellationToken cancellationToken)
- => _apiClient.GroupIdentifyAsync(type, key, properties, cancellationToken, distinctId);
+ {
+ if (IsDisabled(nameof(GroupIdentifyAsync)))
+ {
+ return Task.FromResult(new ApiResult(0));
+ }
+
+ return _apiClient.GroupIdentifyAsync(type, key, properties, cancellationToken, distinctId);
+ }
///
public bool Capture(
@@ -162,6 +216,11 @@ public bool Capture(
bool sendFeatureFlags,
DateTimeOffset? timestamp = null)
{
+ if (IsDisabled(nameof(Capture)))
+ {
+ return false;
+ }
+
// If custom timestamp provided, add it to properties
if (timestamp.HasValue)
{
@@ -218,6 +277,11 @@ public bool CaptureException(
bool sendFeatureFlags,
DateTimeOffset? timestamp = null)
{
+ if (IsDisabled(nameof(CaptureException)))
+ {
+ return false;
+ }
+
if (exception == null)
{
_logger.LogErrorCaptureExceptionNull();
@@ -305,6 +369,16 @@ public async Task IsFeatureEnabledAsync(
FeatureFlagOptions? options,
CancellationToken cancellationToken)
{
+ if (IsDisabled(nameof(IsFeatureEnabledAsync)))
+ {
+ return false;
+ }
+
+ if (RequiresMissingPersonalApiKey(options, nameof(IsFeatureEnabledAsync)))
+ {
+ return false;
+ }
+
var result = await GetFeatureFlagAsync(
featureKey,
distinctId,
@@ -321,6 +395,16 @@ public async Task IsFeatureEnabledAsync(
FeatureFlagOptions? options,
CancellationToken cancellationToken)
{
+ if (IsDisabled(nameof(GetFeatureFlagAsync)))
+ {
+ return null;
+ }
+
+ if (RequiresMissingPersonalApiKey(options, nameof(GetFeatureFlagAsync)))
+ {
+ return null;
+ }
+
LocalEvaluator? localEvaluator;
try
{
@@ -461,9 +545,13 @@ void HandleRemoteError(Exception ex, string errorType)
///
public async Task GetRemoteConfigPayloadAsync(string key, CancellationToken cancellationToken)
{
- if (_options.Value.PersonalApiKey is null)
+ if (IsDisabled(nameof(GetRemoteConfigPayloadAsync)))
+ {
+ return null;
+ }
+
+ if (IsPersonalApiKeyMissing(nameof(GetRemoteConfigPayloadAsync)))
{
- _logger.LogWarningPersonalApiKeyRequiredForRemoteConfigPayload();
return null;
}
@@ -565,6 +653,16 @@ public async Task> GetAllFeatureFlagsAs
AllFeatureFlagsOptions? options,
CancellationToken cancellationToken)
{
+ if (IsDisabled(nameof(GetAllFeatureFlagsAsync)))
+ {
+ return new Dictionary();
+ }
+
+ if (RequiresMissingPersonalApiKey(options, nameof(GetAllFeatureFlagsAsync)))
+ {
+ return new Dictionary();
+ }
+
if (_options.Value.PersonalApiKey is not null)
{
// Attempt to load local feature flags.
@@ -650,9 +748,13 @@ public async Task LoadFeatureFlagsAsync(CancellationToken cancellationToken)
{
_logger.LogInfoLoadFeatureFlags();
- if (_options.Value.PersonalApiKey is null)
+ if (IsDisabled(nameof(LoadFeatureFlagsAsync)))
+ {
+ return;
+ }
+
+ if (IsPersonalApiKeyMissing(nameof(LoadFeatureFlagsAsync)))
{
- _logger.LogWarningPersonalApiKeyRequired();
return;
}
@@ -678,7 +780,15 @@ public async Task LoadFeatureFlagsAsync(CancellationToken cancellationToken)
}
///
- public async Task FlushAsync() => await _asyncBatchHandler.FlushAsync();
+ public async Task FlushAsync()
+ {
+ if (IsDisabled(nameof(FlushAsync)))
+ {
+ return;
+ }
+
+ await _asyncBatchHandler.FlushAsync();
+ }
///
public string Version => VersionConstants.Version;
@@ -688,6 +798,11 @@ public async Task LoadFeatureFlagsAsync(CancellationToken cancellationToken)
[Obsolete("This method is for internal use only and may go away soon.")]
internal async Task GetLocalEvaluatorAsync(CancellationToken cancellationToken)
{
+ if (IsDisabled(nameof(GetLocalEvaluatorAsync)))
+ {
+ return null;
+ }
+
try
{
return await _featureFlagsLoader.GetFeatureFlagsForLocalEvaluationAsync(cancellationToken);
@@ -705,7 +820,15 @@ public async Task LoadFeatureFlagsAsync(CancellationToken cancellationToken)
///
/// Clears the local flags cache.
///
- public void ClearLocalFlagsCache() => _featureFlagsLoader.Clear();
+ public void ClearLocalFlagsCache()
+ {
+ if (IsDisabled(nameof(ClearLocalFlagsCache)))
+ {
+ return;
+ }
+
+ _featureFlagsLoader.Clear();
+ }
///
public async ValueTask DisposeAsync()
@@ -798,12 +921,6 @@ public static partial void LogWarnCaptureFailed(
[LoggerMessage(
EventId = 9,
- Level = LogLevel.Warning,
- Message = "[FEATURE FLAGS] You have to specify a personal_api_key to fetch remote config payloads.")]
- public static partial void LogWarningPersonalApiKeyRequiredForRemoteConfigPayload(this ILogger logger);
-
- [LoggerMessage(
- EventId = 10,
Level = LogLevel.Error,
Message = "[FEATURE FLAGS] Error while fetching remote config payload.")]
public static partial void LogErrorUnableToGetRemoteConfigPayload(
@@ -811,68 +928,74 @@ public static partial void LogErrorUnableToGetRemoteConfigPayload(
Exception exception);
[LoggerMessage(
- EventId = 11,
+ EventId = 10,
Level = LogLevel.Error,
Message = "[FEATURE FLAGS] Unable to get feature flags and payloads")]
public static partial void LogErrorUnableToGetFeatureFlagsAndPayloads(this ILogger logger, Exception exception);
[LoggerMessage(
- EventId = 12,
+ EventId = 11,
Level = LogLevel.Warning,
Message = "[FEATURE FLAGS] Quota exceeded, resetting feature flag data. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts")]
public static partial void LogWarningQuotaExceeded(this ILogger logger);
[LoggerMessage(
- EventId = 13,
+ EventId = 12,
Level = LogLevel.Warning,
Message = "[FEATURE FLAGS] Quota exceeded, resetting feature flag data. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts")]
public static partial void LogWarningQuotaExceeded(this ILogger logger, Exception e);
[LoggerMessage(
- EventId = 14,
+ EventId = 13,
Level = LogLevel.Warning,
Message = "ProjectApiKey is deprecated and will be removed in the next major version. Use ProjectToken instead.")]
public static partial void LogWarningProjectApiKeyDeprecated(this ILogger logger);
[LoggerMessage(
- EventId = 15,
+ EventId = 14,
Level = LogLevel.Error,
Message = "Either ProjectToken or ProjectApiKey must be provided.")]
public static partial void LogErrorProjectTokenRequired(this ILogger logger);
[LoggerMessage(
- EventId = 16,
+ EventId = 15,
Level = LogLevel.Information,
Message = "[FEATURE FLAGS] Loading feature flags for local evaluation")]
public static partial void LogInfoLoadFeatureFlags(this ILogger logger);
[LoggerMessage(
- EventId = 17,
- Level = LogLevel.Warning,
- Message = "[FEATURE FLAGS] You have to specify a personal_api_key to use feature flags.")]
- public static partial void LogWarningPersonalApiKeyRequired(this ILogger logger);
-
- [LoggerMessage(
- EventId = 18,
+ EventId = 16,
Level = LogLevel.Debug,
Message = "[FEATURE FLAGS] Feature flags loaded successfully, polling {PollingStatus}")]
public static partial void LogDebugFeatureFlagsLoaded(this ILogger logger, string pollingStatus);
[LoggerMessage(
- EventId = 18,
+ EventId = 17,
Level = LogLevel.Error,
Message = "[FEATURE FLAGS] Failed to load feature flags")]
public static partial void LogErrorFailedToLoadFeatureFlags(this ILogger logger, Exception exception);
[LoggerMessage(
- EventId = 19,
+ EventId = 18,
Level = LogLevel.Error,
Message = "CaptureException called with null exception")]
public static partial void LogErrorCaptureExceptionNull(this ILogger logger);
[LoggerMessage(
- EventId = 20,
+ EventId = 19,
Level = LogLevel.Error,
Message = "CaptureException failed with an exception")]
public static partial void LogErrorCaptureExceptionFailed(this ILogger logger, Exception exception);
+
+ [LoggerMessage(
+ EventId = 20,
+ Level = LogLevel.Warning,
+ Message = "PostHog SDK is disabled; {MethodName} is a no-op.")]
+ public static partial void LogWarningClientDisabled(this ILogger logger, string methodName);
+
+ [LoggerMessage(
+ EventId = 21,
+ Level = LogLevel.Warning,
+ Message = "PostHog personal_api_key is not configured; {MethodName} is a no-op.")]
+ public static partial void LogWarningPersonalApiKeyMissing(this ILogger logger, string methodName);
}
diff --git a/tests/UnitTests/PostHogClientTests.cs b/tests/UnitTests/PostHogClientTests.cs
index 9dbc287..c2896d8 100644
--- a/tests/UnitTests/PostHogClientTests.cs
+++ b/tests/UnitTests/PostHogClientTests.cs
@@ -1404,6 +1404,166 @@ private static void AssertContextEmpty(JsonElement frame)
}
}
+public class TheDisabledClient
+{
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" \n\t ")]
+ public async Task NoOpsWhenProjectTokenIsMissingEmptyOrWhitespace(string? projectToken)
+ {
+ var container = new TestContainer(services =>
+ {
+ services.Configure(options =>
+ {
+ options.ProjectToken = projectToken;
+ options.PersonalApiKey = "fake-personal-api-key";
+ });
+ });
+ var captureHandler = container.FakeHttpMessageHandler.AddCaptureResponse();
+ var batchHandler = container.FakeHttpMessageHandler.AddBatchResponse();
+ var flagsHandler = container.FakeHttpMessageHandler.AddFlagsResponse("""{"featureFlags": {"flag-key": true}}""");
+ var localEvaluationHandler = container.FakeHttpMessageHandler.AddLocalEvaluationResponse("""{"flags": []}""");
+ var client = container.Activate();
+
+ var aliasResult = await client.AliasAsync("previous-id", "new-id", CancellationToken.None);
+ var identifyResult = await client.IdentifyAsync("distinct-id");
+ var groupResult = await client.GroupIdentifyAsync("organization", "id:5", null, CancellationToken.None);
+ var groupWithDistinctIdResult = await client.GroupIdentifyAsync("distinct-id", "organization", "id:5", null, CancellationToken.None);
+ var captured = client.Capture("distinct-id", "some-event");
+ var capturedException = client.CaptureException(new InvalidOperationException("boom"), "distinct-id");
+ await client.FlushAsync();
+ var isFeatureEnabled = await client.IsFeatureEnabledAsync("flag-key", "distinct-id");
+ var featureFlag = await client.GetFeatureFlagAsync("flag-key", "distinct-id", null, CancellationToken.None);
+ var remoteConfigPayload = await client.GetRemoteConfigPayloadAsync("config-key", CancellationToken.None);
+ var allFlags = await client.GetAllFeatureFlagsAsync("distinct-id", null, CancellationToken.None);
+ await client.LoadFeatureFlagsAsync();
+
+ Assert.Equal(0, aliasResult.Status);
+ Assert.Equal(0, identifyResult.Status);
+ Assert.Equal(0, groupResult.Status);
+ Assert.Equal(0, groupWithDistinctIdResult.Status);
+ Assert.False(captured);
+ Assert.False(capturedException);
+ Assert.False(isFeatureEnabled);
+ Assert.Null(featureFlag);
+ Assert.Null(remoteConfigPayload);
+ Assert.Empty(allFlags);
+ Assert.Empty(captureHandler.ReceivedRequests);
+ Assert.Empty(batchHandler.ReceivedRequests);
+ Assert.Empty(flagsHandler.ReceivedRequests);
+ Assert.Empty(localEvaluationHandler.ReceivedRequests);
+ AssertDisabledLog(container, nameof(PostHogClient.AliasAsync));
+ AssertDisabledLog(container, nameof(PostHogClient.IdentifyAsync));
+ AssertDisabledLog(container, nameof(PostHogClient.GroupIdentifyAsync));
+ AssertDisabledLog(container, nameof(PostHogClient.Capture));
+ AssertDisabledLog(container, nameof(PostHogClient.CaptureException));
+ AssertDisabledLog(container, nameof(PostHogClient.FlushAsync));
+ AssertDisabledLog(container, nameof(PostHogClient.IsFeatureEnabledAsync));
+ AssertDisabledLog(container, nameof(PostHogClient.GetFeatureFlagAsync));
+ AssertDisabledLog(container, nameof(PostHogClient.GetRemoteConfigPayloadAsync));
+ AssertDisabledLog(container, nameof(PostHogClient.GetAllFeatureFlagsAsync));
+ AssertDisabledLog(container, nameof(PostHogClient.LoadFeatureFlagsAsync));
+
+ var options = ((IOptions)((IServiceProvider)container).GetService(typeof(IOptions))!).Value;
+ Assert.Null(options.ProjectToken);
+ Assert.True(options.Disabled);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(" \n\t ")]
+ public async Task NoOpsWhenLegacyProjectApiKeyIsEmptyOrWhitespace(string projectApiKey)
+ {
+ var container = new TestContainer(services =>
+ {
+ services.Configure(options =>
+ {
+ options.ProjectToken = null;
+ options.PersonalApiKey = "fake-personal-api-key";
+#pragma warning disable CS0618
+ options.ProjectApiKey = projectApiKey;
+#pragma warning restore CS0618
+ });
+ });
+ var captureHandler = container.FakeHttpMessageHandler.AddCaptureResponse();
+ var batchHandler = container.FakeHttpMessageHandler.AddBatchResponse();
+ var flagsHandler = container.FakeHttpMessageHandler.AddFlagsResponse("""{"featureFlags": {"flag-key": true}}""");
+ var localEvaluationHandler = container.FakeHttpMessageHandler.AddLocalEvaluationResponse("""{"flags": []}""");
+ var client = container.Activate();
+
+ var identifyResult = await client.IdentifyAsync("distinct-id");
+ var captured = client.Capture("distinct-id", "some-event");
+ await client.FlushAsync();
+ var isFeatureEnabled = await client.IsFeatureEnabledAsync("flag-key", "distinct-id");
+ var allFlags = await client.GetAllFeatureFlagsAsync("distinct-id", null, CancellationToken.None);
+ await client.LoadFeatureFlagsAsync();
+
+ Assert.Equal(0, identifyResult.Status);
+ Assert.False(captured);
+ Assert.False(isFeatureEnabled);
+ Assert.Empty(allFlags);
+ Assert.Empty(captureHandler.ReceivedRequests);
+ Assert.Empty(batchHandler.ReceivedRequests);
+ Assert.Empty(flagsHandler.ReceivedRequests);
+ Assert.Empty(localEvaluationHandler.ReceivedRequests);
+ AssertDisabledLog(container, nameof(PostHogClient.Capture));
+
+ var options = ((IOptions)((IServiceProvider)container).GetService(typeof(IOptions))!).Value;
+ Assert.Null(options.ProjectToken);
+ Assert.True(options.Disabled);
+ }
+
+ static void AssertDisabledLog(TestContainer container, string methodName)
+ {
+ var warningLogs = container.FakeLoggerProvider.GetAllEvents(minimumLevel: LogLevel.Warning);
+ Assert.Contains(warningLogs, log =>
+ log.Message?.Contains("PostHog SDK is disabled", StringComparison.Ordinal) == true
+ && log.Message.Contains(methodName, StringComparison.Ordinal));
+ }
+}
+
+public class ThePersonalApiKeyProtectedMethods
+{
+ [Fact]
+ public async Task NoOpWhenPersonalApiKeyIsMissing()
+ {
+ var container = new TestContainer();
+ var flagsHandler = container.FakeHttpMessageHandler.AddFlagsResponse("""{"featureFlags": {"flag-key": true}}""");
+ var localEvaluationHandler = container.FakeHttpMessageHandler.AddLocalEvaluationResponse("""{"flags": []}""");
+ var remoteConfigHandler = container.FakeHttpMessageHandler.AddRemoteConfigResponse("config-key", """{"enabled": true}""");
+ var client = container.Activate();
+
+ var onlyEvaluateLocally = new FeatureFlagOptions { OnlyEvaluateLocally = true };
+ var isFeatureEnabled = await client.IsFeatureEnabledAsync("flag-key", "distinct-id", onlyEvaluateLocally, CancellationToken.None);
+ var featureFlag = await client.GetFeatureFlagAsync("flag-key", "distinct-id", onlyEvaluateLocally, CancellationToken.None);
+ var allFlags = await client.GetAllFeatureFlagsAsync("distinct-id", onlyEvaluateLocally, CancellationToken.None);
+ var remoteConfigPayload = await client.GetRemoteConfigPayloadAsync("config-key", CancellationToken.None);
+ await client.LoadFeatureFlagsAsync();
+
+ Assert.False(isFeatureEnabled);
+ Assert.Null(featureFlag);
+ Assert.Empty(allFlags);
+ Assert.Null(remoteConfigPayload);
+ Assert.Empty(flagsHandler.ReceivedRequests);
+ Assert.Empty(localEvaluationHandler.ReceivedRequests);
+ Assert.Empty(remoteConfigHandler.ReceivedRequests);
+ AssertPersonalApiKeyMissingLog(container, nameof(PostHogClient.IsFeatureEnabledAsync));
+ AssertPersonalApiKeyMissingLog(container, nameof(PostHogClient.GetFeatureFlagAsync));
+ AssertPersonalApiKeyMissingLog(container, nameof(PostHogClient.GetAllFeatureFlagsAsync));
+ AssertPersonalApiKeyMissingLog(container, nameof(PostHogClient.GetRemoteConfigPayloadAsync));
+ AssertPersonalApiKeyMissingLog(container, nameof(PostHogClient.LoadFeatureFlagsAsync));
+ }
+
+ static void AssertPersonalApiKeyMissingLog(TestContainer container, string methodName)
+ {
+ var warningLogs = container.FakeLoggerProvider.GetAllEvents(minimumLevel: LogLevel.Warning);
+ Assert.Contains(warningLogs, log =>
+ log.Message?.Contains("personal_api_key is not configured", StringComparison.Ordinal) == true
+ && log.Message.Contains(methodName, StringComparison.Ordinal));
+ }
+}
+
public class TheLoadFeatureFlagsAsyncMethod
{
[Fact]
@@ -1432,10 +1592,10 @@ public async Task LogsWarningWhenPersonalApiKeyIsNull()
await client.LoadFeatureFlagsAsync();
- // Verify warning was logged
var warningLogs = container.FakeLoggerProvider.GetAllEvents(minimumLevel: LogLevel.Warning);
Assert.Contains(warningLogs, log =>
- log.Message?.Contains("You have to specify a personal_api_key to use feature flags", StringComparison.Ordinal) == true);
+ log.Message?.Contains("personal_api_key is not configured", StringComparison.Ordinal) == true
+ && log.Message.Contains(nameof(PostHogClient.LoadFeatureFlagsAsync), StringComparison.Ordinal));
}
[Fact]
@@ -1448,7 +1608,8 @@ public async Task LogsWarningWhenPersonalApiKeyIsBlankAfterTrimmingWhitespace()
var warningLogs = container.FakeLoggerProvider.GetAllEvents(minimumLevel: LogLevel.Warning);
Assert.Contains(warningLogs, log =>
- log.Message?.Contains("You have to specify a personal_api_key to use feature flags", StringComparison.Ordinal) == true);
+ log.Message?.Contains("personal_api_key is not configured", StringComparison.Ordinal) == true
+ && log.Message.Contains(nameof(PostHogClient.LoadFeatureFlagsAsync), StringComparison.Ordinal));
}
[Fact]