diff --git a/client/api/analytics/AnalyticsManager.cs b/client/api/analytics/AnalyticsManager.cs index d838b26..a22392e 100644 --- a/client/api/analytics/AnalyticsManager.cs +++ b/client/api/analytics/AnalyticsManager.cs @@ -96,6 +96,13 @@ private void PushToEvaluationAnalyticsCache(FeatureConfig featureConfig, Variati private void PushToTargetAnalyticsCache(Target target) { + + if (target.IsPrivate) + { + // Target is marked as private, so don't send it in analytics + return; + } + if (analyticsPublisherService.IsTargetSeen(target)) { // Target has already been processed in a previous interval, so ignore it. @@ -118,6 +125,8 @@ private void PushToTargetAnalyticsCache(Target target) // change did not go as far as to maintain two caches (due to effort involved), but differentiate them based on subclassing, so // the counter used for target metrics isn't needed, but causes no issue. targetAnalyticsCache.Put(targetAnalytics, 1); + + analyticsPublisherService.MarkTargetAsSeen(target); } private void LogMetricsIgnoredWarning(string cacheType, int cacheSize, int bufferSize) diff --git a/client/api/analytics/AnalyticsPublisherService.cs b/client/api/analytics/AnalyticsPublisherService.cs index 4e38832..7c63a0f 100644 --- a/client/api/analytics/AnalyticsPublisherService.cs +++ b/client/api/analytics/AnalyticsPublisherService.cs @@ -17,9 +17,7 @@ internal class AnalyticsPublisherService private static readonly string VariationIdentifierAttribute = "variationIdentifier"; private static readonly string TargetAttribute = "target"; internal static readonly ConcurrentDictionary SeenTargets = new(); - private static readonly ConcurrentDictionary StagingSeenTargets = new(); private static readonly string SdkType = "SDK_TYPE"; - private static readonly string AnonymousTarget = "anonymous"; private static readonly string Server = "server"; private static readonly string SdkLanguage = "SDK_LANGUAGE"; private static readonly string SdkVersion = "SDK_VERSION"; @@ -55,9 +53,7 @@ public void SendDataAndResetCache() logger.LogDebug("Sending analytics data :{@a}", metrics); connector.PostMetrics(metrics); } - - foreach (var uniqueTarget in StagingSeenTargets.Keys) SeenTargets.TryAdd(uniqueTarget, 0); - StagingSeenTargets.Clear(); + logger.LogDebug("Successfully sent analytics data to the server"); evaluationAnalyticsCache.resetCache(); targetAnalyticsCache.resetCache(); @@ -94,7 +90,6 @@ public void SendDataAndResetCache() SetMetricsAttributes(metricsData, VariationValueAttribute, evaluation.Variation.Value); SetMetricsAttributes(metricsData, TargetAttribute, evaluation.Target.Identifier); SetCommonSdkAttributes(metricsData); - StagingSeenTargets.TryAdd(evaluation.Target, 0); metrics.MetricsData.Add(metricsData); } @@ -102,7 +97,7 @@ public void SendDataAndResetCache() foreach (var targetAnalytic in targetsCache) { var target = targetAnalytic.Key.Target; - if (target != null && !SeenTargets.ContainsKey(target) && !target.IsPrivate) + if (target != null) { var targetData = new TargetData { @@ -111,15 +106,6 @@ public void SendDataAndResetCache() Attributes = new List() }; - // Add target attributes, respecting private attributes - foreach (var attribute in target.Attributes) - if (target.PrivateAttributes == null || !target.PrivateAttributes.Contains(attribute.Key)) - targetData.Attributes.Add(new KeyValue - { Key = attribute.Key, Value = attribute.Value }); - - // Add to StagingSeenTargets for future reference - StagingSeenTargets.TryAdd(target, 0); - metrics.TargetData.Add(targetData); } } @@ -132,6 +118,12 @@ public bool IsTargetSeen(Target target) { return SeenTargets.ContainsKey(target); } + + public void MarkTargetAsSeen(Target target) + { + SeenTargets.TryAdd(target, 0); + } + private void SetCommonSdkAttributes(MetricsData metricsData) { diff --git a/client/dto/Target.cs b/client/dto/Target.cs index b1a1c30..76d07a0 100644 --- a/client/dto/Target.cs +++ b/client/dto/Target.cs @@ -20,9 +20,8 @@ public Target() { attributes = new Dictionary(); } - - [Obsolete("isPrivate and privateAttributes will be removed in a future release use other constructor instead")] - public Target(string identifier, string name, Dictionary attributes, bool isPrivate, HashSet privateAttributes) + + public Target(string identifier, string name, Dictionary attributes) { if (attributes == null) { @@ -35,11 +34,20 @@ public Target(string identifier, string name, Dictionary attribu Identifier = identifier; Name = name; - IsPrivate = isPrivate; - PrivateAttributes = privateAttributes; + IsPrivate = false; + } + + public Target(string identifier, string name, Dictionary attributes, bool isPrivate) + { + this.attributes = attributes ?? new Dictionary(); + this.identifier = identifier; + this.name = name; + this.isPrivate = isPrivate; } - public Target(string identifier, string name, Dictionary attributes) + + [Obsolete("privateAttributes will be removed in a future release. Use Target(string identifier, string name, Dictionary attributes, bool isPrivate) to mark the entire target as private.")] + public Target(string identifier, string name, Dictionary attributes, bool isPrivate, HashSet privateAttributes) { if (attributes == null) { @@ -52,14 +60,13 @@ public Target(string identifier, string name, Dictionary attribu Identifier = identifier; Name = name; - IsPrivate = false; + IsPrivate = isPrivate; + PrivateAttributes = privateAttributes; } public string Name { get => name; set => name = value; } public string Identifier { get => identifier; set => identifier = value; } public Dictionary Attributes { get => attributes; set => attributes = value; } - - [Obsolete("Private attributes will be removed in a future release")] public bool IsPrivate { get => isPrivate; set => isPrivate = value; } [Obsolete("Private attributes will be removed in a future release")] @@ -149,7 +156,6 @@ public TargetBuilder Attributes(Dictionary attributes) return this; } - [Obsolete("Private attributes will be removed in a future release")] public TargetBuilder IsPrivate(bool isPrivate) { this.isPrivate = isPrivate; diff --git a/docs/further_reading.md b/docs/further_reading.md index 95aa53e..54b1ea7 100644 --- a/docs/further_reading.md +++ b/docs/further_reading.md @@ -24,6 +24,20 @@ CfClient.Instance.Initialize(apiKey, Config.Builder() | enableStream | SetStreamEnabled(true) | Enable streaming mode. | true | | enableAnalytics | SetAnalyticsEnabled(true) | Enable analytics. Metrics data is posted every 60s | true | +# Anonymous Target + +If you do not want a `Target` to be sent to Harness servers, you can use the `isPrivate` field. + +```csharp +Target target1 = Target.builder() + .Identifier("myIdentifier") + .Name("myName") + .IsPrivate(true) + .Attributes(new Dictionary(){{"email", "demo@harness.io"}}) + .build(); +``` + + ## Logging Configuration You can configure the logger using Serilog. Add Serilog to your project with the following commands diff --git a/ff-netF48-server-sdk.csproj b/ff-netF48-server-sdk.csproj index 6686d3f..fa2a15d 100644 --- a/ff-netF48-server-sdk.csproj +++ b/ff-netF48-server-sdk.csproj @@ -8,10 +8,10 @@ ff-dotnet-server-sdk io.harness.cfsdk false - 1.5.0 + 1.6.0 true - 1.5.0 - 1.5.0 + 1.6.0 + 1.6.0 support@harness.io Copyright © 2023 https://harness.io/icon-ff.svg