|
1 | 1 | # frozen_string_literal: true |
2 | 2 |
|
3 | | -require 'zlib' |
4 | | -require 'thread' |
5 | | - |
6 | | -require 'datadog/appsec/api_security/lru_cache' |
7 | | - |
8 | | -require_relative 'event' |
| 3 | +require_relative 'models/event' |
| 4 | +require_relative 'deduplicator' |
9 | 5 |
|
10 | 6 | module Datadog |
11 | 7 | module OpenFeature |
12 | 8 | module Exposures |
13 | 9 | class Reporter |
14 | | - DEFAULT_CACHE_LIMIT = 1_000 |
15 | | - |
16 | | - def initialize(worker:, cache: nil, logger: Datadog.logger, time_provider: Time) |
| 10 | + def initialize(worker, telemetry:, logger: Datadog.logger) |
17 | 11 | @worker = worker |
18 | 12 | @logger = logger |
19 | | - @time_provider = time_provider |
20 | | - @cache = cache || Datadog::AppSec::APISecurity::LRUCache.new(DEFAULT_CACHE_LIMIT) |
21 | | - @cache_mutex = Mutex.new |
| 13 | + @telemetry = telemetry |
| 14 | + @deduplicator = Deduplicator.new |
22 | 15 | end |
23 | 16 |
|
24 | | - def report(result:, context: nil) |
25 | | - payload = normalize(result, context) |
26 | | - return false if payload.nil? |
27 | | - |
28 | | - cache_key = payload[:cache_key] |
29 | | - digest = payload[:digest] |
30 | | - |
31 | | - return false if cache_key && digest && duplicate?(cache_key, digest) |
| 17 | + def report(result, context:) |
| 18 | + return false unless result.dig('result', 'flagMetadata', 'doLog') |
32 | 19 |
|
33 | | - event = build_event(payload) |
34 | | - return false if event.nil? |
| 20 | + event = Models::Event.build(result, context: context) |
| 21 | + return false if @deduplicator.duplicate?(event) |
35 | 22 |
|
36 | 23 | @worker.enqueue(event) |
37 | 24 | rescue => e |
38 | 25 | @logger.debug { "OpenFeature: Reporter failed to enqueue exposure: #{e.class}: #{e.message}" } |
39 | | - false |
40 | | - end |
41 | | - |
42 | | - def flush |
43 | | - @cache_mutex.synchronize { @cache.clear } |
44 | | - end |
45 | | - |
46 | | - private |
47 | | - |
48 | | - def duplicate?(cache_key, digest) |
49 | | - @cache_mutex.synchronize do |
50 | | - stored = @cache[cache_key] |
51 | | - return true if stored == digest |
52 | | - |
53 | | - @cache.store(cache_key, digest) |
54 | | - false |
55 | | - end |
56 | | - end |
57 | | - |
58 | | - def normalize(result, context) |
59 | | - data = ensure_hash(result) |
60 | | - result_data = ensure_hash(data[:result] || data['result']) |
61 | | - flag_metadata = ensure_hash(result_data[:flagMetadata] || result_data['flagMetadata']) |
62 | | - |
63 | | - flag_key = data[:flag] || data['flag'] |
64 | | - subject_id = extract_subject_id(data, context) |
65 | | - allocation_key = flag_metadata[:allocationKey] || flag_metadata['allocationKey'] |
66 | | - evaluation_key = result_data[:variant] || result_data['variant'] |
67 | | - |
68 | | - return nil if flag_key.nil? || subject_id.nil? |
69 | | - |
70 | | - variant = evaluation_key || result_data[:value] || result_data['value'] |
71 | | - variant_key = variant.nil? ? nil : variant.to_s |
72 | | - return nil if variant_key.nil? |
73 | 26 |
|
74 | | - { |
75 | | - flag_key: flag_key, |
76 | | - subject_id: subject_id, |
77 | | - subject_type: nil, |
78 | | - subject_attributes: extract_attributes(data, context), |
79 | | - allocation_key: allocation_key, |
80 | | - variant_key: variant_key, |
81 | | - cache_key: cache_key(flag_key, subject_id), |
82 | | - digest: allocation_key.nil? ? nil : digest(allocation_key, variant_key) |
83 | | - } |
84 | | - end |
85 | | - |
86 | | - def build_event(payload) |
87 | | - Event.new( |
88 | | - timestamp: @time_provider.now, |
89 | | - allocation_key: payload[:allocation_key], |
90 | | - flag_key: payload[:flag_key], |
91 | | - variant_key: payload[:variant_key], |
92 | | - subject_id: payload[:subject_id], |
93 | | - subject_type: payload[:subject_type], |
94 | | - subject_attributes: payload[:subject_attributes] |
95 | | - ) |
96 | | - end |
97 | | - |
98 | | - def extract_subject_id(data, context) |
99 | | - data[:targetingKey] || data['targetingKey'] || context_value(context, :targeting_key) || |
100 | | - context_value(context, :targetingKey) || context_value(context, :targetingkey) |
101 | | - end |
102 | | - |
103 | | - def extract_attributes(data, context) |
104 | | - attributes = data[:attributes] || data['attributes'] |
105 | | - return attributes if attributes.is_a?(Hash) |
106 | | - |
107 | | - context_value(context, :attributes).is_a?(Hash) ? context_value(context, :attributes) : nil |
108 | | - end |
109 | | - |
110 | | - def context_value(context, key) |
111 | | - case context |
112 | | - when Hash |
113 | | - context[key] || context[key.to_s] |
114 | | - else |
115 | | - context.respond_to?(key) ? context.public_send(key) : nil |
116 | | - end |
117 | | - end |
118 | | - |
119 | | - def ensure_hash(value) |
120 | | - value.is_a?(Hash) ? value : {} |
121 | | - end |
122 | | - |
123 | | - def cache_key(flag_key, subject_id) |
124 | | - return nil if flag_key.nil? || subject_id.nil? |
125 | | - |
126 | | - "#{flag_key}:#{subject_id}" |
127 | | - end |
128 | | - |
129 | | - def digest(allocation_key, evaluation_key) |
130 | | - Zlib.crc32("#{allocation_key}:#{evaluation_key}") |
| 27 | + false |
131 | 28 | end |
132 | 29 | end |
133 | 30 | end |
|
0 commit comments