@@ -111,15 +111,16 @@ Rule::Rule(const ProtoRule& rule, Regex::Engine& regex_engine, absl::Status& cre
111111
112112absl::StatusOr<ConfigSharedPtr>
113113Config::create (const  envoy::extensions::filters::http::header_to_metadata::v3::Config& config,
114-                Regex::Engine& regex_engine, bool  per_route) {
114+                Regex::Engine& regex_engine, Stats::Scope& scope,  bool  per_route) {
115115  absl::Status creation_status = absl::OkStatus ();
116-   auto  cfg = ConfigSharedPtr (new  Config (config, regex_engine, per_route, creation_status));
116+   auto  cfg = ConfigSharedPtr (new  Config (config, regex_engine, scope,  per_route, creation_status));
117117  RETURN_IF_NOT_OK_REF (creation_status);
118118  return  cfg;
119119}
120120
121121Config::Config (const  envoy::extensions::filters::http::header_to_metadata::v3::Config config,
122-                Regex::Engine& regex_engine, const  bool  per_route, absl::Status& creation_status) {
122+                Regex::Engine& regex_engine, Stats::Scope& scope, const  bool  per_route,
123+                absl::Status& creation_status) {
123124  absl::StatusOr<bool > request_set_or =
124125      Config::configToVector (config.request_rules (), request_rules_, regex_engine);
125126  SET_AND_RETURN_IF_NOT_OK (request_set_or.status (), creation_status);
@@ -130,6 +131,11 @@ Config::Config(const envoy::extensions::filters::http::header_to_metadata::v3::C
130131  SET_AND_RETURN_IF_NOT_OK (response_set_or.status (), creation_status);
131132  response_set_ = response_set_or.value ();
132133
134+   //  Generate stats only if stat_prefix is configured (opt-in behavior).
135+   if  (!config.stat_prefix ().empty ()) {
136+     stats_.emplace (generateStats (config.stat_prefix (), scope));
137+   }
138+ 
133139  //  Note: empty configs are fine for the global config, which would be the case for enabling
134140  //        the filter globally without rules and then applying them at the virtual host or
135141  //        route level. At the virtual or route level, it makes no sense to have an empty
@@ -158,6 +164,12 @@ absl::StatusOr<bool> Config::configToVector(const ProtobufRepeatedRule& proto_ru
158164  return  true ;
159165}
160166
167+ HeaderToMetadataFilterStats Config::generateStats (const  std::string& stat_prefix,
168+                                                   Stats::Scope& scope) {
169+   const  std::string final_prefix = fmt::format (" http_filter_name.{}"  , stat_prefix);
170+   return  {ALL_HEADER_TO_METADATA_FILTER_STATS (POOL_COUNTER_PREFIX (scope, final_prefix))};
171+ }
172+ 
161173HeaderToMetadataFilter::HeaderToMetadataFilter (const  ConfigSharedPtr config) : config_(config) {}
162174
163175HeaderToMetadataFilter::~HeaderToMetadataFilter () = default ;
@@ -166,7 +178,8 @@ Http::FilterHeadersStatus HeaderToMetadataFilter::decodeHeaders(Http::RequestHea
166178                                                                bool ) {
167179  const  auto * config = getConfig ();
168180  if  (config->doRequest ()) {
169-     writeHeaderToMetadata (headers, config->requestRules (), *decoder_callbacks_);
181+     writeHeaderToMetadata (headers, config->requestRules (), *decoder_callbacks_,
182+                           HeaderDirection::Request);
170183  }
171184
172185  return  Http::FilterHeadersStatus::Continue;
@@ -181,7 +194,8 @@ Http::FilterHeadersStatus HeaderToMetadataFilter::encodeHeaders(Http::ResponseHe
181194                                                                bool ) {
182195  const  auto * config = getConfig ();
183196  if  (config->doResponse ()) {
184-     writeHeaderToMetadata (headers, config->responseRules (), *encoder_callbacks_);
197+     writeHeaderToMetadata (headers, config->responseRules (), *encoder_callbacks_,
198+                           HeaderDirection::Response);
185199  }
186200  return  Http::FilterHeadersStatus::Continue;
187201}
@@ -193,21 +207,28 @@ void HeaderToMetadataFilter::setEncoderFilterCallbacks(
193207
194208bool  HeaderToMetadataFilter::addMetadata (StructMap& struct_map, const  std::string& meta_namespace,
195209                                         const  std::string& key, std::string value, ValueType type,
196-                                          ValueEncode encode) const  {
210+                                          ValueEncode encode, HeaderDirection direction ) const  {
197211  ProtobufWkt::Value val;
212+   const  auto * config = getConfig ();
198213
199214  ASSERT (!value.empty ());
200215
201216  if  (value.size () >= MAX_HEADER_VALUE_LEN) {
202217    //  Too long, go away.
203218    ENVOY_LOG (debug, " metadata value is too long"  );
219+     if  (config->stats ().has_value ()) {
220+       config->stats ().value ().header_value_too_long_ .inc ();
221+     }
204222    return  false ;
205223  }
206224
207225  if  (encode == envoy::extensions::filters::http::header_to_metadata::v3::Config::BASE64) {
208226    value = Base64::decodeWithoutPadding (value);
209227    if  (value.empty ()) {
210228      ENVOY_LOG (debug, " Base64 decode failed"  );
229+       if  (config->stats ().has_value ()) {
230+         config->stats ().value ().base64_decode_failed_ .inc ();
231+       }
211232      return  false ;
212233    }
213234  }
@@ -240,6 +261,15 @@ bool HeaderToMetadataFilter::addMetadata(StructMap& struct_map, const std::strin
240261  auto & keyval = struct_map[meta_namespace];
241262  (*keyval.mutable_fields ())[key] = std::move (val);
242263
264+   //  Increment metadata_added stat if stats are enabled.
265+   if  (config->stats ().has_value ()) {
266+     if  (direction == HeaderDirection::Request) {
267+       config->stats ().value ().request_metadata_added_ .inc ();
268+     } else  {
269+       config->stats ().value ().response_metadata_added_ .inc ();
270+     }
271+   }
272+ 
243273  return  true ;
244274}
245275
@@ -249,38 +279,68 @@ const std::string& HeaderToMetadataFilter::decideNamespace(const std::string& ns
249279
250280//  add metadata['key']= value depending on header present or missing case
251281void  HeaderToMetadataFilter::applyKeyValue (std::string&& value, const  Rule& rule,
252-                                            const  KeyValuePair& keyval, StructMap& np) {
282+                                            const  KeyValuePair& keyval, StructMap& np,
283+                                            HeaderDirection direction) {
284+   const  auto * config = getConfig ();
285+ 
253286  if  (!keyval.value ().empty ()) {
254287    value = keyval.value ();
255288  } else  {
256289    const  auto & matcher = rule.regexRewrite ();
257290    if  (matcher != nullptr ) {
291+       std::string original_value = value;
258292      value = matcher->replaceAll (value, rule.regexSubstitution ());
293+       //  If we had a non-empty input but got an empty result from regex, it could indicate a
294+       //  failure.
295+       if  (!original_value.empty () && value.empty ()) {
296+         if  (config->stats ().has_value ()) {
297+           config->stats ().value ().regex_substitution_failed_ .inc ();
298+         }
299+       }
259300    }
260301  }
261302  if  (!value.empty ()) {
262303    const  auto & nspace = decideNamespace (keyval.metadata_namespace ());
263-     addMetadata (np, nspace, keyval.key (), value, keyval.type (), keyval.encode ());
304+     addMetadata (np, nspace, keyval.key (), value, keyval.type (), keyval.encode (), direction );
264305  } else  {
265306    ENVOY_LOG (debug, " value is empty, not adding metadata"  );
266307  }
267308}
268309
269310void  HeaderToMetadataFilter::writeHeaderToMetadata (Http::HeaderMap& headers,
270311                                                   const  HeaderToMetadataRules& rules,
271-                                                    Http::StreamFilterCallbacks& callbacks) {
312+                                                    Http::StreamFilterCallbacks& callbacks,
313+                                                    HeaderDirection direction) {
272314  StructMap structs_by_namespace;
315+   const  auto * config = getConfig ();
273316
274317  for  (const  auto & rule : rules) {
275318    const  auto & proto_rule = rule.rule ();
276319    absl::optional<std::string> value = rule.selector_ ->extract (headers);
277320
321+     //  Increment rules_processed stat if stats are enabled.
322+     if  (config->stats ().has_value ()) {
323+       if  (direction == HeaderDirection::Request) {
324+         config->stats ().value ().request_rules_processed_ .inc ();
325+       } else  {
326+         config->stats ().value ().response_rules_processed_ .inc ();
327+       }
328+     }
329+ 
278330    if  (value && proto_rule.has_on_header_present ()) {
279331      applyKeyValue (std::move (value).value_or (" "  ), rule, proto_rule.on_header_present (),
280-                     structs_by_namespace);
332+                     structs_by_namespace, direction );
281333    } else  if  (!value && proto_rule.has_on_header_missing ()) {
334+       //  Increment header_not_found stat if stats are enabled.
335+       if  (config->stats ().has_value ()) {
336+         if  (direction == HeaderDirection::Request) {
337+           config->stats ().value ().request_header_not_found_ .inc ();
338+         } else  {
339+           config->stats ().value ().response_header_not_found_ .inc ();
340+         }
341+       }
282342      applyKeyValue (std::move (value).value_or (" "  ), rule, proto_rule.on_header_missing (),
283-                     structs_by_namespace);
343+                     structs_by_namespace, direction );
284344    }
285345  }
286346  //  Any matching rules?
0 commit comments