@@ -352,11 +352,14 @@ feature_server:
352352 push: true # push request counters
353353 materialization: true # materialization counters & duration
354354 freshness: true # feature freshness gauges
355+ offline_features: true # offline store retrieval counters & latency
356+ audit_logging: false # structured JSON audit logs (see below)
355357` ` `
356358
357359Any category set to `false` will emit no metrics and start no background
358360threads (e.g., setting `freshness : false` prevents the registry polling
359- thread from starting). All categories default to `true`.
361+ thread from starting). All categories default to `true` except
362+ ` audit_logging` , which defaults to `false`.
360363
361364# ## Available metrics
362365
@@ -375,6 +378,9 @@ thread from starting). All categories default to `true`.
375378| `feast_materialization_result_total` | Counter | `feature_view`, `status` | `materialization` | Materialization runs (success/failure) |
376379| `feast_materialization_duration_seconds` | Histogram | `feature_view` | `materialization` | Materialization duration per feature view |
377380| `feast_feature_freshness_seconds` | Gauge | `feature_view`, `project` | `freshness` | Seconds since last materialization |
381+ | `feast_offline_store_request_total` | Counter | `method`, `status` | `offline_features` | Total offline store retrieval requests |
382+ | `feast_offline_store_request_latency_seconds` | Histogram | `method` | `offline_features` | Latency of offline store retrieval operations |
383+ | `feast_offline_store_row_count` | Histogram | `method` | `offline_features` | Rows returned by offline store retrieval |
378384
379385# ## Per-ODFV transformation metrics
380386
@@ -405,6 +411,70 @@ The `odfv_name` label lets you filter or group by individual ODFV,
405411and the `mode` label (`python`, `pandas`, `substrait`) lets you compare
406412transformation engines.
407413
414+ # ## Audit logging
415+
416+ Feast can emit structured JSON audit log entries for every online and offline
417+ feature retrieval. These are written via the standard `feast.audit` Python
418+ logger, so you can route them to a dedicated file, SIEM, or log aggregator
419+ independently of application logs.
420+
421+ Audit logging is **disabled by default**. Enable it in `feature_store.yaml` :
422+
423+ ` ` ` yaml
424+ feature_server:
425+ type: local
426+ metrics:
427+ enabled: true
428+ audit_logging: true
429+ ` ` `
430+
431+ **Online audit log** (emitted per `/get-online-features` call):
432+
433+ ` ` ` json
434+ {
435+ "event": "online_feature_request",
436+ "timestamp": "2026-05-11T08:30:00.123456+00:00",
437+ "requestor_id": "[email protected] ", 438+ "entity_keys": ["driver_id"],
439+ "entity_count": 3,
440+ "feature_views": ["driver_hourly_stats"],
441+ "feature_count": 3,
442+ "status": "success",
443+ "latency_ms": 12.34
444+ }
445+ ` ` `
446+
447+ **Offline audit log** (emitted per `RetrievalJob.to_arrow()` call):
448+
449+ ` ` ` json
450+ {
451+ "event": "offline_feature_retrieval",
452+ "timestamp": "2026-05-11T08:31:00.456789+00:00",
453+ "method": "to_arrow",
454+ "start_time": "2026-05-11T08:30:59.226789+00:00",
455+ "end_time": "2026-05-11T08:31:00.456789+00:00",
456+ "feature_views": ["driver_hourly_stats"],
457+ "feature_count": 3,
458+ "row_count": 500,
459+ "status": "success",
460+ "duration_ms": 1230.0
461+ }
462+ ` ` `
463+
464+ The `requestor_id` field in online audit logs is populated from the
465+ security manager's current user when authentication is configured, and
466+ falls back to `"anonymous"` otherwise.
467+
468+ To route audit logs to a separate file :
469+
470+ ` ` ` python
471+ import logging
472+
473+ handler = logging.FileHandler("/var/log/feast/audit.log")
474+ handler.setFormatter(logging.Formatter("%(message)s"))
475+ logging.getLogger("feast.audit").addHandler(handler)
476+ ` ` `
477+
408478# ## Scraping with Prometheus
409479
410480` ` ` yaml
0 commit comments