@@ -95,16 +95,42 @@ static void NormalizeOptions(PostHogOptions options, ILogger<PostHogClient> logg
9595 logger . LogWarningProjectApiKeyDeprecated ( ) ;
9696 }
9797
98- options . ProjectToken = options . ProjectToken ? . Trim ( ) ;
99- options . PersonalApiKey = options . PersonalApiKey . NullIfEmpty ( ) ;
100- options . HostUrl = options . HostUrl . NormalizeHostUrl ( ) ;
98+ options . Normalize ( ) ;
10199
102- if ( string . IsNullOrEmpty ( options . ProjectToken ) )
100+ if ( options . ProjectToken is null )
103101 {
104102 logger . LogErrorProjectTokenRequired ( ) ;
105103 }
106104 }
107105
106+ bool IsDisabled ( string methodName )
107+ {
108+ if ( ! _options . Value . Disabled )
109+ {
110+ return false ;
111+ }
112+
113+ _logger . LogWarningClientDisabled ( methodName ) ;
114+ return true ;
115+ }
116+
117+ // A personal_api_key is only required for feature flag calls when callers explicitly request
118+ // local-only evaluation. Without it we cannot download local flag definitions, and the
119+ // local-only option means we must not fall back to remote /flags evaluation.
120+ bool RequiresMissingPersonalApiKey ( AllFeatureFlagsOptions ? options , string methodName )
121+ => options is { OnlyEvaluateLocally : true } && IsPersonalApiKeyMissing ( methodName ) ;
122+
123+ bool IsPersonalApiKeyMissing ( string methodName )
124+ {
125+ if ( _options . Value . PersonalApiKey is not null )
126+ {
127+ return false ;
128+ }
129+
130+ _logger . LogWarningPersonalApiKeyMissing ( methodName ) ;
131+ return true ;
132+ }
133+
108134 /// <summary>
109135 /// To marry up whatever a user does before they sign up or log in with what they do after you need to make an
110136 /// alias call. This will allow you to answer questions like "Which marketing channels leads to users churning
@@ -122,27 +148,48 @@ public async Task<ApiResult> AliasAsync(
122148 string previousId ,
123149 string newId ,
124150 CancellationToken cancellationToken )
125- => await _apiClient . AliasAsync ( previousId , newId , cancellationToken ) ;
151+ {
152+ if ( IsDisabled ( nameof ( AliasAsync ) ) )
153+ {
154+ return new ApiResult ( 0 ) ;
155+ }
156+
157+ return await _apiClient . AliasAsync ( previousId , newId , cancellationToken ) ;
158+ }
126159
127160 /// <inheritdoc/>
128161 public async Task < ApiResult > IdentifyAsync (
129162 string distinctId ,
130163 Dictionary < string , object > ? personPropertiesToSet ,
131164 Dictionary < string , object > ? personPropertiesToSetOnce ,
132165 CancellationToken cancellationToken )
133- => await _apiClient . IdentifyAsync (
166+ {
167+ if ( IsDisabled ( nameof ( IdentifyAsync ) ) )
168+ {
169+ return new ApiResult ( 0 ) ;
170+ }
171+
172+ return await _apiClient . IdentifyAsync (
134173 distinctId ,
135174 personPropertiesToSet ,
136175 personPropertiesToSetOnce ,
137176 cancellationToken ) ;
177+ }
138178
139179 /// <inheritdoc/>
140180 public Task < ApiResult > GroupIdentifyAsync (
141181 string type ,
142182 StringOrValue < int > key ,
143183 Dictionary < string , object > ? properties ,
144184 CancellationToken cancellationToken )
145- => _apiClient . GroupIdentifyAsync ( type , key , properties , cancellationToken ) ;
185+ {
186+ if ( IsDisabled ( nameof ( GroupIdentifyAsync ) ) )
187+ {
188+ return Task . FromResult ( new ApiResult ( 0 ) ) ;
189+ }
190+
191+ return _apiClient . GroupIdentifyAsync ( type , key , properties , cancellationToken ) ;
192+ }
146193
147194 /// <inheritdoc/>
148195 public Task < ApiResult > GroupIdentifyAsync (
@@ -151,7 +198,14 @@ public Task<ApiResult> GroupIdentifyAsync(
151198 StringOrValue < int > key ,
152199 Dictionary < string , object > ? properties ,
153200 CancellationToken cancellationToken )
154- => _apiClient . GroupIdentifyAsync ( type , key , properties , cancellationToken , distinctId ) ;
201+ {
202+ if ( IsDisabled ( nameof ( GroupIdentifyAsync ) ) )
203+ {
204+ return Task . FromResult ( new ApiResult ( 0 ) ) ;
205+ }
206+
207+ return _apiClient . GroupIdentifyAsync ( type , key , properties , cancellationToken , distinctId ) ;
208+ }
155209
156210 /// <inheritdoc/>
157211 public bool Capture (
@@ -162,6 +216,11 @@ public bool Capture(
162216 bool sendFeatureFlags ,
163217 DateTimeOffset ? timestamp = null )
164218 {
219+ if ( IsDisabled ( nameof ( Capture ) ) )
220+ {
221+ return false ;
222+ }
223+
165224 // If custom timestamp provided, add it to properties
166225 if ( timestamp . HasValue )
167226 {
@@ -218,6 +277,11 @@ public bool CaptureException(
218277 bool sendFeatureFlags ,
219278 DateTimeOffset ? timestamp = null )
220279 {
280+ if ( IsDisabled ( nameof ( CaptureException ) ) )
281+ {
282+ return false ;
283+ }
284+
221285 if ( exception == null )
222286 {
223287 _logger . LogErrorCaptureExceptionNull ( ) ;
@@ -305,6 +369,16 @@ public async Task<bool> IsFeatureEnabledAsync(
305369 FeatureFlagOptions ? options ,
306370 CancellationToken cancellationToken )
307371 {
372+ if ( IsDisabled ( nameof ( IsFeatureEnabledAsync ) ) )
373+ {
374+ return false ;
375+ }
376+
377+ if ( RequiresMissingPersonalApiKey ( options , nameof ( IsFeatureEnabledAsync ) ) )
378+ {
379+ return false ;
380+ }
381+
308382 var result = await GetFeatureFlagAsync (
309383 featureKey ,
310384 distinctId ,
@@ -321,6 +395,16 @@ public async Task<bool> IsFeatureEnabledAsync(
321395 FeatureFlagOptions ? options ,
322396 CancellationToken cancellationToken )
323397 {
398+ if ( IsDisabled ( nameof ( GetFeatureFlagAsync ) ) )
399+ {
400+ return null ;
401+ }
402+
403+ if ( RequiresMissingPersonalApiKey ( options , nameof ( GetFeatureFlagAsync ) ) )
404+ {
405+ return null ;
406+ }
407+
324408 LocalEvaluator ? localEvaluator ;
325409 try
326410 {
@@ -461,9 +545,13 @@ void HandleRemoteError(Exception ex, string errorType)
461545 /// <inheritdoc/>
462546 public async Task < JsonDocument ? > GetRemoteConfigPayloadAsync ( string key , CancellationToken cancellationToken )
463547 {
464- if ( _options . Value . PersonalApiKey is null )
548+ if ( IsDisabled ( nameof ( GetRemoteConfigPayloadAsync ) ) )
549+ {
550+ return null ;
551+ }
552+
553+ if ( IsPersonalApiKeyMissing ( nameof ( GetRemoteConfigPayloadAsync ) ) )
465554 {
466- _logger . LogWarningPersonalApiKeyRequiredForRemoteConfigPayload ( ) ;
467555 return null ;
468556 }
469557
@@ -565,6 +653,16 @@ public async Task<IReadOnlyDictionary<string, FeatureFlag>> GetAllFeatureFlagsAs
565653 AllFeatureFlagsOptions ? options ,
566654 CancellationToken cancellationToken )
567655 {
656+ if ( IsDisabled ( nameof ( GetAllFeatureFlagsAsync ) ) )
657+ {
658+ return new Dictionary < string , FeatureFlag > ( ) ;
659+ }
660+
661+ if ( RequiresMissingPersonalApiKey ( options , nameof ( GetAllFeatureFlagsAsync ) ) )
662+ {
663+ return new Dictionary < string , FeatureFlag > ( ) ;
664+ }
665+
568666 if ( _options . Value . PersonalApiKey is not null )
569667 {
570668 // Attempt to load local feature flags.
@@ -650,9 +748,13 @@ public async Task LoadFeatureFlagsAsync(CancellationToken cancellationToken)
650748 {
651749 _logger . LogInfoLoadFeatureFlags ( ) ;
652750
653- if ( _options . Value . PersonalApiKey is null )
751+ if ( IsDisabled ( nameof ( LoadFeatureFlagsAsync ) ) )
752+ {
753+ return ;
754+ }
755+
756+ if ( IsPersonalApiKeyMissing ( nameof ( LoadFeatureFlagsAsync ) ) )
654757 {
655- _logger . LogWarningPersonalApiKeyRequired ( ) ;
656758 return ;
657759 }
658760
@@ -678,7 +780,15 @@ public async Task LoadFeatureFlagsAsync(CancellationToken cancellationToken)
678780 }
679781
680782 /// <inheritdoc/>
681- public async Task FlushAsync ( ) => await _asyncBatchHandler . FlushAsync ( ) ;
783+ public async Task FlushAsync ( )
784+ {
785+ if ( IsDisabled ( nameof ( FlushAsync ) ) )
786+ {
787+ return ;
788+ }
789+
790+ await _asyncBatchHandler . FlushAsync ( ) ;
791+ }
682792
683793 /// <inheritdoc/>
684794 public string Version => VersionConstants . Version ;
@@ -688,6 +798,11 @@ public async Task LoadFeatureFlagsAsync(CancellationToken cancellationToken)
688798 [ Obsolete ( "This method is for internal use only and may go away soon." ) ]
689799 internal async Task < LocalEvaluator ? > GetLocalEvaluatorAsync ( CancellationToken cancellationToken )
690800 {
801+ if ( IsDisabled ( nameof ( GetLocalEvaluatorAsync ) ) )
802+ {
803+ return null ;
804+ }
805+
691806 try
692807 {
693808 return await _featureFlagsLoader . GetFeatureFlagsForLocalEvaluationAsync ( cancellationToken ) ;
@@ -705,7 +820,15 @@ public async Task LoadFeatureFlagsAsync(CancellationToken cancellationToken)
705820 /// <summary>
706821 /// Clears the local flags cache.
707822 /// </summary>
708- public void ClearLocalFlagsCache ( ) => _featureFlagsLoader . Clear ( ) ;
823+ public void ClearLocalFlagsCache ( )
824+ {
825+ if ( IsDisabled ( nameof ( ClearLocalFlagsCache ) ) )
826+ {
827+ return ;
828+ }
829+
830+ _featureFlagsLoader . Clear ( ) ;
831+ }
709832
710833 /// <inheritdoc/>
711834 public async ValueTask DisposeAsync ( )
@@ -796,12 +919,6 @@ public static partial void LogWarnCaptureFailed(
796919 int propertiesCount ,
797920 int count ) ;
798921
799- [ LoggerMessage (
800- EventId = 9 ,
801- Level = LogLevel . Warning ,
802- Message = "[FEATURE FLAGS] You have to specify a personal_api_key to fetch remote config payloads." ) ]
803- public static partial void LogWarningPersonalApiKeyRequiredForRemoteConfigPayload ( this ILogger < PostHogClient > logger ) ;
804-
805922 [ LoggerMessage (
806923 EventId = 10 ,
807924 Level = LogLevel . Error ,
@@ -846,12 +963,6 @@ public static partial void LogErrorUnableToGetRemoteConfigPayload(
846963 Message = "[FEATURE FLAGS] Loading feature flags for local evaluation" ) ]
847964 public static partial void LogInfoLoadFeatureFlags ( this ILogger < PostHogClient > logger ) ;
848965
849- [ LoggerMessage (
850- EventId = 17 ,
851- Level = LogLevel . Warning ,
852- Message = "[FEATURE FLAGS] You have to specify a personal_api_key to use feature flags." ) ]
853- public static partial void LogWarningPersonalApiKeyRequired ( this ILogger < PostHogClient > logger ) ;
854-
855966 [ LoggerMessage (
856967 EventId = 18 ,
857968 Level = LogLevel . Debug ,
@@ -875,4 +986,16 @@ public static partial void LogErrorUnableToGetRemoteConfigPayload(
875986 Level = LogLevel . Error ,
876987 Message = "CaptureException failed with an exception" ) ]
877988 public static partial void LogErrorCaptureExceptionFailed ( this ILogger < PostHogClient > logger , Exception exception ) ;
989+
990+ [ LoggerMessage (
991+ EventId = 21 ,
992+ Level = LogLevel . Warning ,
993+ Message = "PostHog SDK is disabled; {MethodName} is a no-op." ) ]
994+ public static partial void LogWarningClientDisabled ( this ILogger < PostHogClient > logger , string methodName ) ;
995+
996+ [ LoggerMessage (
997+ EventId = 22 ,
998+ Level = LogLevel . Warning ,
999+ Message = "PostHog personal_api_key is not configured; {MethodName} is a no-op." ) ]
1000+ public static partial void LogWarningPersonalApiKeyMissing ( this ILogger < PostHogClient > logger , string methodName ) ;
8781001}
0 commit comments