Repository: Kong/kubernetes-ingress-controller
Pull request #6346 (2024-07-23 23:52:25)

chore(*) prepare 3.2.3 patch release
**What this PR does / why we need it**:

3.2.3 for cross-namespace plugin ref fix. Also includes an internal perf fix cause why not.

**Special notes for your reviewer**:

Release issue https://github.com/Kong/kubernetes-ingress-controller/issues/6344

https://github.com/Kong/docs.konghq.com/pull/7578 for the associated doc FWIW.

Changelog to main with backport at https://github.com/Kong/kubernetes-ingress-controller/pull/6345

pmalek: Any reason to not cherry pick each commit 1 by 1 and creating separate PRs for easier history inspection in the future? 
rainest: > Any reason to not cherry pick each commit 1 by 1 and creating separate PRs for easier history inspection in the future?

Extra reviews and maybe merge conflicts depending on the order (not in this case though).

What workflow/history review would need them separate? As long as we rebase merge the commits remain separate, so AFAIK all we lose is the title indication we'd get if we squashed and retitled to include the PR. Origin PR is still available in the click-through view (e.g. https://github.com/Kong/kubernetes-ingress-controller/commit/641c73c3897db358ac6c0693003b985e2d4e3eed has no title, but still lists it in the Github metadata), which IMO is good enough for something we probably don't check that often.
pmalek: > Extra reviews and maybe merge conflicts depending on the order (not in this case though).

I see extra reviews as a good thing rather than a problem since there's more opportunities to spot a potential issue ( even though such a thing shouldn't happen with simple cherry picking).

Conflicts shouldn't arise either if the patches are applied in chronological order.

> What workflow/history review would need them separate? As long as we rebase merge the commits remain separate, so AFAIK all we lose is the title indication we'd get if we squashed and retitled to include the PR. Origin PR is still available in the click-through view (e.g. [641c73c](https://github.com/Kong/kubernetes-ingress-controller/commit/641c73c3897db358ac6c0693003b985e2d4e3eed) has no title, but still lists it in the Github metadata), which IMO is good enough for something we probably don't check that often.

GitHub's UI to review each commit from a PR somewhat works but comments from each of them is bound in a weird way which makes tracing back the commented commit hard.

---

I do see this as a shortcut which can save some review back and forth but at the same time it can make error slipping through the review cracks easier.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fee67f7d4..b4abd3c4f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,8 @@ Adding a new version? You'll need three changes:
  - [3.0.2](#302)
  - [3.0.1](#301)
  - [3.0.0](#300)
+ - [2.12.5](#2125)
+ - [2.12.4](#2124)
  - [2.12.3](#2123)
  - [2.12.2](#2122)
  - [2.12.1](#2121)
@@ -91,6 +93,22 @@ Adding a new version? You'll need three changes:
  - [0.0.5](#005)
  - [0.0.4 and prior](#004-and-prior)
 
+## Unreleased
+
+### Added
+
+- `KongCustomEntity` is now supported by the `FallbackConfiguration` feature.
+  [#6286](https://github.com/Kong/kubernetes-ingress-controller/pull/6286)
+
+### Fixed
+
+- Services using `Secret`s containing the same certificate as client certificates
+  by annotation `konghq.com/client-cert` can be correctly translated.
+  [#6228](https://github.com/Kong/kubernetes-ingress-controller/pull/6228)
+- Generate one entity for each attached foreign entity if a `KongCustomEntity`
+  resource is attached to multiple foreign Kong entities.
+  [#6280](https://github.com/Kong/kubernetes-ingress-controller/pull/6280)
+
 ## 3.2.2
 
 > Release date: 2024-07-01
@@ -793,6 +811,26 @@ Adding a new version? You'll need three changes:
 [KongIngress to KongUpstreamPolicy migration guide]: https://docs.konghq.com/kubernetes-ingress-controller/latest/guides/migrate/kongingress/
 [Migrate Credential Type Labels]: https://docs.konghq.com/kubernetes-ingress-controller/latest/guides/migrate/credential-kongcredtype-label/
 
+## [2.12.5]
+
+> Release date: 2024-06-25
+
+### Fixed
+
+- Services using `Secret`s containing the same certificate as client certificates
+  by annotation `konghq.com/client-cert` can be correctly translated.
+  [#6228](https://github.com/Kong/kubernetes-ingress-controller/pull/6228)
+
+## [2.12.4]
+
+> Release date: 2024-04-30
+
+- Bump golang version to 1.21.9 to fix CVE [GO-2024-2687](https://pkg.go.dev/vuln/GO-2024-2687).
+  [#5916](https://github.com/Kong/kubernetes-ingress-controller/pull/5916)
+- Bump `golang.org/x/net` to `0.23.0` and `google.golang.org/protobuf` to `1.33.0`
+  To fix [GO-2024-2687](https://pkg.go.dev/vuln/GO-2024-2687) and [GO-2024-2611](https://pkg.go.dev/vuln/GO-2024-2611).
+  [#5947](https://github.com/Kong/kubernetes-ingress-controller/pull/5947)
+
 ## [2.12.3]
 
 > Release date: 2023-12-19
@@ -3585,6 +3623,9 @@ Please read the changelog and test in your environment.
 [3.0.2]: https://github.com/kong/kubernetes-ingress-controller/compare/v3.0.1...v3.0.2
 [3.0.1]: https://github.com/kong/kubernetes-ingress-controller/compare/v3.0.0...v3.0.1
 [3.0.0]: https://github.com/kong/kubernetes-ingress-controller/compare/v2.12.0...v3.0.0
+[2.12.5]: https://github.com/kong/kubernetes-ingress-controller/compare/v2.12.4...v2.12.5
+[2.12.4]: https://github.com/kong/kubernetes-ingress-controller/compare/v2.12.3...v2.12.4
+[2.12.3]: https://github.com/kong/kubernetes-ingress-controller/compare/v2.12.2...v2.12.3
 [2.12.2]: https://github.com/kong/kubernetes-ingress-controller/compare/v2.12.1...v2.12.2
 [2.12.1]: https://github.com/kong/kubernetes-ingress-controller/compare/v2.12.0...v2.12.1
 [2.12.0]: https://github.com/kong/kubernetes-ingress-controller/compare/v2.11.1...v2.12.0
diff --git a/controllers/license/konglicense_controller.go b/controllers/license/konglicense_controller.go
index ca9772d21..213819c0f 100644
--- a/controllers/license/konglicense_controller.go
+++ b/controllers/license/konglicense_controller.go
@@ -28,7 +28,7 @@ import (
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/controllers"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/controllers/crds"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object/status"
 	kongv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1alpha1"
 )
@@ -183,7 +183,7 @@ func (r *KongV1Alpha1KongLicenseReconciler) Reconcile(ctx context.Context, req c
 			}
 			if objectExistsInCache {
 				// Delete the object in the cache first.
-				log.V(util.DebugLevel).Info("KongLicense deleted in cluster, delete it in cache")
+				log.V(logging.DebugLevel).Info("KongLicense deleted in cluster, delete it in cache")
 				if err := r.LicenseCache.Delete(obj); err != nil {
 					return ctrl.Result{}, err
 				}
@@ -197,11 +197,11 @@ func (r *KongV1Alpha1KongLicenseReconciler) Reconcile(ctx context.Context, req c
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongLicense")
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongLicense")
 
 		_, objectExistsInCache, err := r.LicenseCache.Get(obj)
 		if err != nil {
@@ -230,7 +230,7 @@ func (r *KongV1Alpha1KongLicenseReconciler) Reconcile(ctx context.Context, req c
 	// Trigger a compare on stored KongLicenses in cache and pick the newest.
 	chosenLicense := r.pickLicenseInCache()
 	if chosenLicense.Name == obj.Name {
-		log.V(util.DebugLevel).Info("Picked KongLicense being reconciled", "name", obj.Name)
+		log.V(logging.DebugLevel).Info("Picked KongLicense being reconciled", "name", obj.Name)
 		err := r.ensureControllerStatusConditions(ctx, obj, metav1.ConditionTrue, ConditionReasonPickedAsLatest, "")
 		if err != nil {
 			return ctrl.Result{}, err
@@ -238,7 +238,7 @@ func (r *KongV1Alpha1KongLicenseReconciler) Reconcile(ctx context.Context, req c
 
 		oldChosenLicense := r.getChosenLicense()
 		if oldChosenLicense != nil && oldChosenLicense.Name != chosenLicense.Name {
-			r.Log.V(util.DebugLevel).Info("Originally picked KongLicense replaced", "name", oldChosenLicense.Name)
+			r.Log.V(logging.DebugLevel).Info("Originally picked KongLicense replaced", "name", oldChosenLicense.Name)
 			err := r.ensureControllerStatusConditions(ctx, oldChosenLicense, metav1.ConditionFalse, ConditionReasonReplacedByNewer, "Replaced by newer created KongLicense")
 			if err != nil {
 				return ctrl.Result{}, err
@@ -264,10 +264,10 @@ type License struct {
 func (r *KongV1Alpha1KongLicenseReconciler) GetValidatedLicense() mo.Option[License] {
 	chosenLicense := r.getChosenLicense()
 	if chosenLicense == nil {
-		r.Log.V(util.DebugLevel).Info("No KongLicense available")
+		r.Log.V(logging.DebugLevel).Info("No KongLicense available")
 		return mo.None[License]()
 	}
-	r.Log.V(util.DebugLevel).Info("Get license from KongLicense resource", "name", chosenLicense.Name)
+	r.Log.V(logging.DebugLevel).Info("Get license from KongLicense resource", "name", chosenLicense.Name)
 	isValid := mo.None[bool]()
 	if r.licenseValidator != nil {
 		isValid = mo.Some(r.licenseValidator(chosenLicense.RawLicenseString) == nil)
@@ -362,7 +362,7 @@ func (r *KongV1Alpha1KongLicenseReconciler) repickLicenseOnDelete(ctx context.Co
 		chosenLicense := r.pickLicenseInCache()
 		r.setChosenLicense(chosenLicense)
 		if chosenLicense != nil {
-			r.Log.V(util.DebugLevel).Info("Picked KongLicense remaining in cache", "name", chosenLicense.Name)
+			r.Log.V(logging.DebugLevel).Info("Picked KongLicense remaining in cache", "name", chosenLicense.Name)
 			return r.ensureControllerStatusConditions(ctx, chosenLicense, metav1.ConditionTrue, ConditionReasonPickedAsLatest, "")
 		}
 	}
diff --git a/hack/generators/controllers/networking/main.go b/hack/generators/controllers/networking/main.go
index 967616663..c5525f123 100644
--- a/hack/generators/controllers/networking/main.go
+++ b/hack/generators/controllers/networking/main.go
@@ -483,7 +483,7 @@ import (
 	ctrlref "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers/reference"
 	ctrlutils "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers/utils"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object/status"
 	kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1"
 	kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1beta1"
@@ -632,11 +632,11 @@ func (r *{{.PackageAlias}}{{.Kind}}Reconciler) Reconcile(ctx context.Context, re
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "{{.Kind}}", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "{{.Kind}}", "namespace", req.Namespace, "name", req.Name)
 		{{if .NeedsUpdateReferences}}
 		// remove reference record where the {{.Kind}} is the referrer
 		if err := ctrlref.DeleteReferencesByReferrer(r.ReferenceIndexers, r.DataplaneClient, obj); err != nil {
@@ -663,16 +663,16 @@ func (r *{{.PackageAlias}}{{.Kind}}Reconciler) Reconcile(ctx context.Context, re
 			// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
 			// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
 			// if none exists.
-			log.V(util.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
+			log.V(logging.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
 		}
 	}
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
-		log.V(util.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
+		log.V(logging.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
 		"namespace", req.Namespace, "name", req.Name, "class", r.IngressClassName)
 		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
 	} else {
-		log.V(util.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
+		log.V(logging.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
 		"class", r.IngressClassName)
 	}
 {{end}}
@@ -704,20 +704,20 @@ func (r *{{.PackageAlias}}{{.Kind}}Reconciler) Reconcile(ctx context.Context, re
 	// if status updates are enabled report the status for the object
 	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
 		{{- if .IngressAddressUpdatesEnabled }}
-		log.V(util.DebugLevel).Info("Determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
 
 		if  !r.DataplaneClient.KubernetesObjectIsConfigured(obj) {
-			log.V(util.DebugLevel).Info("Resource not yet configured in the data-plane", "namespace", req.Namespace, "name", req.Name)
+			log.V(logging.DebugLevel).Info("Resource not yet configured in the data-plane", "namespace", req.Namespace, "name", req.Name)
 			return ctrl.Result{Requeue: true}, nil // requeue until the object has been properly configured
 		}
 
-		log.V(util.DebugLevel).Info("Determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
 		addrs, err := r.DataplaneAddressFinder.GetLoadBalancerAddresses(ctx)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 
-		log.V(util.DebugLevel).Info("Found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
 		updateNeeded, err := ctrlutils.UpdateLoadBalancerIngress(obj, addrs)
 		if err != nil {
 			return ctrl.Result{}, fmt.Errorf("failed to update load balancer address: %w", err)
@@ -725,7 +725,7 @@ func (r *{{.PackageAlias}}{{.Kind}}Reconciler) Reconcile(ctx context.Context, re
 		{{- end }}
 
 		{{- if .ProgrammedCondition.UpdatesEnabled }}
-		log.V(util.DebugLevel).Info("Updating programmed condition status", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Updating programmed condition status", "namespace", req.Namespace, "name", req.Name)
 		configurationStatus := r.DataplaneClient.KubernetesObjectConfigurationStatus(obj)
 		conditions, updateNeeded := ctrlutils.EnsureProgrammedCondition(
 			configurationStatus, 
@@ -740,7 +740,7 @@ func (r *{{.PackageAlias}}{{.Kind}}Reconciler) Reconcile(ctx context.Context, re
 		if updateNeeded {
 			return ctrl.Result{}, r.Status().Update(ctx, obj)
 		}
-		log.V(util.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
 	}
 {{- end}}
 
diff --git a/internal/admission/validator.go b/internal/admission/validator.go
index aeb6c52d0..c3372b5f9 100644
--- a/internal/admission/validator.go
+++ b/internal/admission/validator.go
@@ -23,6 +23,7 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/kongstate"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/translator"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
 	kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1"
@@ -237,12 +238,12 @@ func (validator KongHTTPValidator) ValidateConsumerGroup(
 	}
 	info, err := infoSvc.Get(ctx)
 	if err != nil {
-		validator.Logger.V(util.DebugLevel).Info("Failed to fetch Kong info", "error", err)
+		validator.Logger.V(logging.DebugLevel).Info("Failed to fetch Kong info", "error", err)
 		return false, ErrTextAdminAPIUnavailable, nil
 	}
 	version, err := kong.NewVersion(info.Version)
 	if err != nil {
-		validator.Logger.V(util.DebugLevel).Info("Failed to parse Kong version", "error", err)
+		validator.Logger.V(logging.DebugLevel).Info("Failed to parse Kong version", "error", err)
 	} else if !version.IsKongGatewayEnterprise() {
 		return false, ErrTextConsumerGroupUnsupported, nil
 	}
@@ -637,7 +638,7 @@ func (validator KongHTTPValidator) ValidateCustomEntity(ctx context.Context, ent
 	schemaService, hasClient := validator.AdminAPIServicesProvider.GetSchemasService()
 	// Skip validation on Kong gateway if we do not have available client.
 	if !hasClient {
-		logger.V(util.DebugLevel).Info("Skipped because no schema service available")
+		logger.V(logging.DebugLevel).Info("Skipped because no schema service available")
 		return true, "", nil
 	}
 
@@ -645,7 +646,7 @@ func (validator KongHTTPValidator) ValidateCustomEntity(ctx context.Context, ent
 	entityType := entity.Spec.EntityType
 	schema, err := schemaService.Get(ctx, entityType)
 	if err != nil {
-		logger.V(util.DebugLevel).Info("Failed to get schema of entity", "entity_type", entityType, "error", err)
+		logger.V(logging.DebugLevel).Info("Failed to get schema of entity", "entity_type", entityType, "error", err)
 		return false, fmt.Sprintf(ErrTextCustomEntityGetSchemaFailed, entityType, err), nil
 	}
 
diff --git a/internal/clients/manager.go b/internal/clients/manager.go
index 42c63cbf0..6b2c5fbbf 100644
--- a/internal/clients/manager.go
+++ b/internal/clients/manager.go
@@ -11,7 +11,7 @@ import (
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/adminapi"
 	dpconf "github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/config"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util/clock"
 )
 
@@ -246,7 +246,7 @@ func (c *AdminAPIClientsManager) gatewayClientsReconciliationLoop() {
 	for {
 		select {
 		case <-c.ctx.Done():
-			c.logger.V(util.InfoLevel).Info("Closing AdminAPIClientsManager", "reason", c.ctx.Err())
+			c.logger.V(logging.InfoLevel).Info("Closing AdminAPIClientsManager", "reason", c.ctx.Err())
 			c.closeGatewayClientsSubscribers()
 			return
 		case discoveredAdminAPIs := <-c.discoveredAdminAPIsNotifyChan:
@@ -261,7 +261,7 @@ func (c *AdminAPIClientsManager) gatewayClientsReconciliationLoop() {
 // It will adjust lists of gateway clients and notify subscribers about the change if readyGatewayClients list has
 // changed.
 func (c *AdminAPIClientsManager) onDiscoveredAdminAPIsNotification(discoveredAdminAPIs []adminapi.DiscoveredAdminAPI) {
-	c.logger.V(util.DebugLevel).Info("Received notification about Admin API addresses change")
+	c.logger.V(logging.DebugLevel).Info("Received notification about Admin API addresses change")
 
 	clientsChanged := c.adjustGatewayClients(discoveredAdminAPIs)
 	readinessChanged := c.reconcileGatewayClientsReadiness()
@@ -273,7 +273,7 @@ func (c *AdminAPIClientsManager) onDiscoveredAdminAPIsNotification(discoveredAdm
 // onReadinessReconciliationTick is called on every readinessReconciliationTicker tick. It will reconcile readiness
 // of all gateway clients and notify subscribers about the change if readyGatewayClients list has changed.
 func (c *AdminAPIClientsManager) onReadinessReconciliationTick() {
-	c.logger.V(util.DebugLevel).Info("Reconciling readiness of gateway clients")
+	c.logger.V(logging.DebugLevel).Info("Reconciling readiness of gateway clients")
 
 	if changed := c.reconcileGatewayClientsReadiness(); changed {
 		c.notifyGatewayClientsSubscribers()
@@ -370,11 +370,11 @@ func (c *AdminAPIClientsManager) reconcileGatewayClientsReadiness() bool {
 
 // notifyGatewayClientsSubscribers sends notifications to all subscribers that have called SubscribeToGatewayClientsChanges.
 func (c *AdminAPIClientsManager) notifyGatewayClientsSubscribers() {
-	c.logger.V(util.DebugLevel).Info("Notifying subscribers about gateway clients change")
+	c.logger.V(logging.DebugLevel).Info("Notifying subscribers about gateway clients change")
 	for _, sub := range c.gatewayClientsChangesSubscribers {
 		select {
 		case <-c.ctx.Done():
-			c.logger.V(util.InfoLevel).Info("Not sending notification to subscribers as the context is done")
+			c.logger.V(logging.InfoLevel).Info("Not sending notification to subscribers as the context is done")
 			return
 		case sub <- struct{}{}:
 		}
diff --git a/internal/clients/readiness.go b/internal/clients/readiness.go
index 8abe640c7..6283b224c 100644
--- a/internal/clients/readiness.go
+++ b/internal/clients/readiness.go
@@ -10,7 +10,7 @@ import (
 	k8stypes "k8s.io/apimachinery/pkg/types"
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/adminapi"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 )
 
 const (
@@ -94,7 +94,7 @@ func (c DefaultReadinessChecker) checkPendingClient(
 	pendingClient adminapi.DiscoveredAdminAPI,
 ) (client *adminapi.Client) {
 	defer func() {
-		c.logger.V(util.DebugLevel).
+		c.logger.V(logging.DebugLevel).
 			Info(fmt.Sprintf("Checking readiness of pending client for %q", pendingClient.Address),
 				"ok", client != nil,
 			)
@@ -105,7 +105,7 @@ func (c DefaultReadinessChecker) checkPendingClient(
 	client, err := c.factory.CreateAdminAPIClient(ctx, pendingClient)
 	if err != nil {
 		// Despite the error reason we still want to keep the client in the pending list to retry later.
-		c.logger.V(util.DebugLevel).Info("Pending client is not ready yet",
+		c.logger.V(logging.DebugLevel).Info("Pending client is not ready yet",
 			"reason", err.Error(),
 			"address", pendingClient.Address,
 		)
@@ -142,7 +142,7 @@ func (c DefaultReadinessChecker) checkAlreadyExistingClients(ctx context.Context
 
 func (c DefaultReadinessChecker) checkAlreadyCreatedClient(ctx context.Context, client AlreadyCreatedClient) (ready bool) {
 	defer func() {
-		c.logger.V(util.DebugLevel).Info(
+		c.logger.V(logging.DebugLevel).Info(
 			fmt.Sprintf("Checking readiness of already created client for %q", client.BaseRootURL()),
 			"ok", ready,
 		)
@@ -152,7 +152,7 @@ func (c DefaultReadinessChecker) checkAlreadyCreatedClient(ctx context.Context,
 	defer cancel()
 	if err := client.IsReady(ctx); err != nil {
 		// Despite the error reason we still want to keep the client in the pending list to retry later.
-		c.logger.V(util.DebugLevel).Info(
+		c.logger.V(logging.DebugLevel).Info(
 			"Already created client is not ready, moving to pending",
 			"address", client.BaseRootURL(),
 			"reason", err.Error(),
diff --git a/internal/controllers/configuration/kongadminapi_controller.go b/internal/controllers/configuration/kongadminapi_controller.go
index 92806e516..3afd78641 100644
--- a/internal/controllers/configuration/kongadminapi_controller.go
+++ b/internal/controllers/configuration/kongadminapi_controller.go
@@ -22,7 +22,7 @@ import (
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/adminapi"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/controllers"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 )
 
 // KongAdminAPIServiceReconciler reconciles Kong Admin API Service Endpointslices
@@ -126,7 +126,7 @@ func (r *KongAdminAPIServiceReconciler) Reconcile(ctx context.Context, req ctrl.
 	r.Log.Info("Reconciling Admin API EndpointSlice", "namespace", req.Namespace, "name", req.Name)
 
 	if !endpoints.DeletionTimestamp.IsZero() {
-		r.Log.V(util.DebugLevel).Info("EndpointSlice is being deleted",
+		r.Log.V(logging.DebugLevel).Info("EndpointSlice is being deleted",
 			"type", "EndpointSlice", "namespace", req.Namespace, "name", req.Name,
 		)
 
@@ -177,7 +177,7 @@ func (r *KongAdminAPIServiceReconciler) Reconcile(ctx context.Context, req ctrl.
 func (r *KongAdminAPIServiceReconciler) notify() {
 	discovered := flattenDiscoveredAdminAPIs(r.Cache)
 	addresses := lo.Map(discovered, func(d adminapi.DiscoveredAdminAPI, _ int) string { return d.Address })
-	r.Log.V(util.DebugLevel).
+	r.Log.V(logging.DebugLevel).
 		Info("Notifying about newly detected Admin APIs", "admin_apis", addresses)
 	r.EndpointsNotifier.Notify(discovered)
 }
diff --git a/internal/controllers/configuration/kongupstreampolicy_controller.go b/internal/controllers/configuration/kongupstreampolicy_controller.go
index 3b0fcffa2..9105ba5f3 100644
--- a/internal/controllers/configuration/kongupstreampolicy_controller.go
+++ b/internal/controllers/configuration/kongupstreampolicy_controller.go
@@ -22,7 +22,7 @@ import (
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/controllers"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object/status"
 	kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1beta1"
 	incubatorv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/incubator/v1alpha1"
@@ -328,11 +328,11 @@ func (r *KongUpstreamPolicyReconciler) Reconcile(ctx context.Context, req ctrl.R
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !kongUpstreamPolicy.DeletionTimestamp.IsZero() && time.Now().After(kongUpstreamPolicy.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongUpstreamPolicy", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongUpstreamPolicy", "namespace", req.Namespace, "name", req.Name)
 
 		objectExistsInCache, err := r.DataplaneClient.ObjectExists(kongUpstreamPolicy)
 		if err != nil {
diff --git a/internal/controllers/configuration/secret_controller.go b/internal/controllers/configuration/secret_controller.go
index 1cddb9627..628625e0e 100644
--- a/internal/controllers/configuration/secret_controller.go
+++ b/internal/controllers/configuration/secret_controller.go
@@ -20,7 +20,7 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/controllers"
 	ctrlref "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers/reference"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/labels"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 )
 
 // -----------------------------------------------------------------------------
@@ -119,11 +119,11 @@ func (r *CoreV1SecretReconciler) Reconcile(ctx context.Context, req ctrl.Request
 		return ctrl.Result{}, err
 	}
 
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !secret.DeletionTimestamp.IsZero() && time.Now().After(secret.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "Secret", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "Secret", "namespace", req.Namespace, "name", req.Name)
 		objectExistsInCache, err := r.DataplaneClient.ObjectExists(secret)
 		if err != nil {
 			return ctrl.Result{}, err
diff --git a/internal/controllers/configuration/zz_generated_controllers.go b/internal/controllers/configuration/zz_generated_controllers.go
index 16398f013..d3fb101f8 100644
--- a/internal/controllers/configuration/zz_generated_controllers.go
+++ b/internal/controllers/configuration/zz_generated_controllers.go
@@ -44,7 +44,7 @@ import (
 	ctrlref "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers/reference"
 	ctrlutils "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers/utils"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object/status"
 	kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1"
 	kongv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1alpha1"
@@ -113,11 +113,11 @@ func (r *CoreV1ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "Service", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "Service", "namespace", req.Namespace, "name", req.Name)
 
 		// remove reference record where the Service is the referrer
 		if err := ctrlref.DeleteReferencesByReferrer(r.ReferenceIndexers, r.DataplaneClient, obj); err != nil {
@@ -209,11 +209,11 @@ func (r *DiscoveryV1EndpointSliceReconciler) Reconcile(ctx context.Context, req
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "EndpointSlice", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "EndpointSlice", "namespace", req.Namespace, "name", req.Name)
 
 		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
@@ -347,11 +347,11 @@ func (r *NetV1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "Ingress", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "Ingress", "namespace", req.Namespace, "name", req.Name)
 
 		// remove reference record where the Ingress is the referrer
 		if err := ctrlref.DeleteReferencesByReferrer(r.ReferenceIndexers, r.DataplaneClient, obj); err != nil {
@@ -378,16 +378,16 @@ func (r *NetV1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request
 			// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
 			// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
 			// if none exists.
-			log.V(util.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
+			log.V(logging.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
 		}
 	}
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
-		log.V(util.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
+		log.V(logging.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
 			"namespace", req.Namespace, "name", req.Name, "class", r.IngressClassName)
 		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
 	} else {
-		log.V(util.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
+		log.V(logging.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
 			"class", r.IngressClassName)
 	}
 
@@ -407,20 +407,20 @@ func (r *NetV1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request
 	}
 	// if status updates are enabled report the status for the object
 	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
-		log.V(util.DebugLevel).Info("Determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
 
 		if !r.DataplaneClient.KubernetesObjectIsConfigured(obj) {
-			log.V(util.DebugLevel).Info("Resource not yet configured in the data-plane", "namespace", req.Namespace, "name", req.Name)
+			log.V(logging.DebugLevel).Info("Resource not yet configured in the data-plane", "namespace", req.Namespace, "name", req.Name)
 			return ctrl.Result{Requeue: true}, nil // requeue until the object has been properly configured
 		}
 
-		log.V(util.DebugLevel).Info("Determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
 		addrs, err := r.DataplaneAddressFinder.GetLoadBalancerAddresses(ctx)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 
-		log.V(util.DebugLevel).Info("Found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
 		updateNeeded, err := ctrlutils.UpdateLoadBalancerIngress(obj, addrs)
 		if err != nil {
 			return ctrl.Result{}, fmt.Errorf("failed to update load balancer address: %w", err)
@@ -428,7 +428,7 @@ func (r *NetV1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request
 		if updateNeeded {
 			return ctrl.Result{}, r.Status().Update(ctx, obj)
 		}
-		log.V(util.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
 	}
 
 	return ctrl.Result{}, nil
@@ -488,11 +488,11 @@ func (r *NetV1IngressClassReconciler) Reconcile(ctx context.Context, req ctrl.Re
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "IngressClass", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "IngressClass", "namespace", req.Namespace, "name", req.Name)
 
 		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
@@ -570,11 +570,11 @@ func (r *KongV1KongIngressReconciler) Reconcile(ctx context.Context, req ctrl.Re
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongIngress", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongIngress", "namespace", req.Namespace, "name", req.Name)
 
 		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
@@ -658,11 +658,11 @@ func (r *KongV1KongPluginReconciler) Reconcile(ctx context.Context, req ctrl.Req
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongPlugin", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongPlugin", "namespace", req.Namespace, "name", req.Name)
 
 		// remove reference record where the KongPlugin is the referrer
 		if err := ctrlref.DeleteReferencesByReferrer(r.ReferenceIndexers, r.DataplaneClient, obj); err != nil {
@@ -795,11 +795,11 @@ func (r *KongV1KongClusterPluginReconciler) Reconcile(ctx context.Context, req c
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongClusterPlugin", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongClusterPlugin", "namespace", req.Namespace, "name", req.Name)
 
 		// remove reference record where the KongClusterPlugin is the referrer
 		if err := ctrlref.DeleteReferencesByReferrer(r.ReferenceIndexers, r.DataplaneClient, obj); err != nil {
@@ -826,16 +826,16 @@ func (r *KongV1KongClusterPluginReconciler) Reconcile(ctx context.Context, req c
 			// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
 			// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
 			// if none exists.
-			log.V(util.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
+			log.V(logging.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
 		}
 	}
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
-		log.V(util.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
+		log.V(logging.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
 			"namespace", req.Namespace, "name", req.Name, "class", r.IngressClassName)
 		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
 	} else {
-		log.V(util.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
+		log.V(logging.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
 			"class", r.IngressClassName)
 	}
 
@@ -966,11 +966,11 @@ func (r *KongV1KongConsumerReconciler) Reconcile(ctx context.Context, req ctrl.R
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongConsumer", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongConsumer", "namespace", req.Namespace, "name", req.Name)
 
 		// remove reference record where the KongConsumer is the referrer
 		if err := ctrlref.DeleteReferencesByReferrer(r.ReferenceIndexers, r.DataplaneClient, obj); err != nil {
@@ -997,16 +997,16 @@ func (r *KongV1KongConsumerReconciler) Reconcile(ctx context.Context, req ctrl.R
 			// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
 			// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
 			// if none exists.
-			log.V(util.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
+			log.V(logging.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
 		}
 	}
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
-		log.V(util.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
+		log.V(logging.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
 			"namespace", req.Namespace, "name", req.Name, "class", r.IngressClassName)
 		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
 	} else {
-		log.V(util.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
+		log.V(logging.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
 			"class", r.IngressClassName)
 	}
 
@@ -1016,7 +1016,7 @@ func (r *KongV1KongConsumerReconciler) Reconcile(ctx context.Context, req ctrl.R
 	}
 	// if status updates are enabled report the status for the object
 	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
-		log.V(util.DebugLevel).Info("Updating programmed condition status", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Updating programmed condition status", "namespace", req.Namespace, "name", req.Name)
 		configurationStatus := r.DataplaneClient.KubernetesObjectConfigurationStatus(obj)
 		conditions, updateNeeded := ctrlutils.EnsureProgrammedCondition(
 			configurationStatus,
@@ -1027,7 +1027,7 @@ func (r *KongV1KongConsumerReconciler) Reconcile(ctx context.Context, req ctrl.R
 		if updateNeeded {
 			return ctrl.Result{}, r.Status().Update(ctx, obj)
 		}
-		log.V(util.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
 	}
 	// update reference relationship from the KongConsumer to other objects.
 	if err := updateReferredObjects(ctx, r.Client, r.ReferenceIndexers, r.DataplaneClient, obj); err != nil {
@@ -1152,11 +1152,11 @@ func (r *KongV1Beta1KongConsumerGroupReconciler) Reconcile(ctx context.Context,
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongConsumerGroup", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongConsumerGroup", "namespace", req.Namespace, "name", req.Name)
 
 		// remove reference record where the KongConsumerGroup is the referrer
 		if err := ctrlref.DeleteReferencesByReferrer(r.ReferenceIndexers, r.DataplaneClient, obj); err != nil {
@@ -1183,16 +1183,16 @@ func (r *KongV1Beta1KongConsumerGroupReconciler) Reconcile(ctx context.Context,
 			// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
 			// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
 			// if none exists.
-			log.V(util.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
+			log.V(logging.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
 		}
 	}
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
-		log.V(util.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
+		log.V(logging.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
 			"namespace", req.Namespace, "name", req.Name, "class", r.IngressClassName)
 		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
 	} else {
-		log.V(util.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
+		log.V(logging.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
 			"class", r.IngressClassName)
 	}
 
@@ -1202,7 +1202,7 @@ func (r *KongV1Beta1KongConsumerGroupReconciler) Reconcile(ctx context.Context,
 	}
 	// if status updates are enabled report the status for the object
 	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
-		log.V(util.DebugLevel).Info("Updating programmed condition status", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Updating programmed condition status", "namespace", req.Namespace, "name", req.Name)
 		configurationStatus := r.DataplaneClient.KubernetesObjectConfigurationStatus(obj)
 		conditions, updateNeeded := ctrlutils.EnsureProgrammedCondition(
 			configurationStatus,
@@ -1213,7 +1213,7 @@ func (r *KongV1Beta1KongConsumerGroupReconciler) Reconcile(ctx context.Context,
 		if updateNeeded {
 			return ctrl.Result{}, r.Status().Update(ctx, obj)
 		}
-		log.V(util.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
 	}
 	// update reference relationship from the KongConsumerGroup to other objects.
 	if err := updateReferredObjects(ctx, r.Client, r.ReferenceIndexers, r.DataplaneClient, obj); err != nil {
@@ -1340,11 +1340,11 @@ func (r *KongV1Beta1TCPIngressReconciler) Reconcile(ctx context.Context, req ctr
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "TCPIngress", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "TCPIngress", "namespace", req.Namespace, "name", req.Name)
 
 		// remove reference record where the TCPIngress is the referrer
 		if err := ctrlref.DeleteReferencesByReferrer(r.ReferenceIndexers, r.DataplaneClient, obj); err != nil {
@@ -1371,16 +1371,16 @@ func (r *KongV1Beta1TCPIngressReconciler) Reconcile(ctx context.Context, req ctr
 			// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
 			// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
 			// if none exists.
-			log.V(util.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
+			log.V(logging.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
 		}
 	}
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
-		log.V(util.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
+		log.V(logging.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
 			"namespace", req.Namespace, "name", req.Name, "class", r.IngressClassName)
 		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
 	} else {
-		log.V(util.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
+		log.V(logging.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
 			"class", r.IngressClassName)
 	}
 
@@ -1400,20 +1400,20 @@ func (r *KongV1Beta1TCPIngressReconciler) Reconcile(ctx context.Context, req ctr
 	}
 	// if status updates are enabled report the status for the object
 	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
-		log.V(util.DebugLevel).Info("Determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
 
 		if !r.DataplaneClient.KubernetesObjectIsConfigured(obj) {
-			log.V(util.DebugLevel).Info("Resource not yet configured in the data-plane", "namespace", req.Namespace, "name", req.Name)
+			log.V(logging.DebugLevel).Info("Resource not yet configured in the data-plane", "namespace", req.Namespace, "name", req.Name)
 			return ctrl.Result{Requeue: true}, nil // requeue until the object has been properly configured
 		}
 
-		log.V(util.DebugLevel).Info("Determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
 		addrs, err := r.DataplaneAddressFinder.GetLoadBalancerAddresses(ctx)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 
-		log.V(util.DebugLevel).Info("Found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
 		updateNeeded, err := ctrlutils.UpdateLoadBalancerIngress(obj, addrs)
 		if err != nil {
 			return ctrl.Result{}, fmt.Errorf("failed to update load balancer address: %w", err)
@@ -1421,7 +1421,7 @@ func (r *KongV1Beta1TCPIngressReconciler) Reconcile(ctx context.Context, req ctr
 		if updateNeeded {
 			return ctrl.Result{}, r.Status().Update(ctx, obj)
 		}
-		log.V(util.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
 	}
 
 	return ctrl.Result{}, nil
@@ -1532,11 +1532,11 @@ func (r *KongV1Beta1UDPIngressReconciler) Reconcile(ctx context.Context, req ctr
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "UDPIngress", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "UDPIngress", "namespace", req.Namespace, "name", req.Name)
 
 		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
@@ -1558,16 +1558,16 @@ func (r *KongV1Beta1UDPIngressReconciler) Reconcile(ctx context.Context, req ctr
 			// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
 			// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
 			// if none exists.
-			log.V(util.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
+			log.V(logging.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
 		}
 	}
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
-		log.V(util.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
+		log.V(logging.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
 			"namespace", req.Namespace, "name", req.Name, "class", r.IngressClassName)
 		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
 	} else {
-		log.V(util.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
+		log.V(logging.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
 			"class", r.IngressClassName)
 	}
 
@@ -1577,20 +1577,20 @@ func (r *KongV1Beta1UDPIngressReconciler) Reconcile(ctx context.Context, req ctr
 	}
 	// if status updates are enabled report the status for the object
 	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
-		log.V(util.DebugLevel).Info("Determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
 
 		if !r.DataplaneClient.KubernetesObjectIsConfigured(obj) {
-			log.V(util.DebugLevel).Info("Resource not yet configured in the data-plane", "namespace", req.Namespace, "name", req.Name)
+			log.V(logging.DebugLevel).Info("Resource not yet configured in the data-plane", "namespace", req.Namespace, "name", req.Name)
 			return ctrl.Result{Requeue: true}, nil // requeue until the object has been properly configured
 		}
 
-		log.V(util.DebugLevel).Info("Determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
 		addrs, err := r.DataplaneAddressFinder.GetLoadBalancerAddresses(ctx)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 
-		log.V(util.DebugLevel).Info("Found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
 		updateNeeded, err := ctrlutils.UpdateLoadBalancerIngress(obj, addrs)
 		if err != nil {
 			return ctrl.Result{}, fmt.Errorf("failed to update load balancer address: %w", err)
@@ -1598,7 +1598,7 @@ func (r *KongV1Beta1UDPIngressReconciler) Reconcile(ctx context.Context, req ctr
 		if updateNeeded {
 			return ctrl.Result{}, r.Status().Update(ctx, obj)
 		}
-		log.V(util.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
 	}
 
 	return ctrl.Result{}, nil
@@ -1658,11 +1658,11 @@ func (r *KongV1Alpha1IngressClassParametersReconciler) Reconcile(ctx context.Con
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "IngressClassParameters", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "IngressClassParameters", "namespace", req.Namespace, "name", req.Name)
 
 		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
@@ -1788,11 +1788,11 @@ func (r *IncubatorV1Alpha1KongServiceFacadeReconciler) Reconcile(ctx context.Con
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongServiceFacade", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongServiceFacade", "namespace", req.Namespace, "name", req.Name)
 
 		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
@@ -1814,16 +1814,16 @@ func (r *IncubatorV1Alpha1KongServiceFacadeReconciler) Reconcile(ctx context.Con
 			// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
 			// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
 			// if none exists.
-			log.V(util.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
+			log.V(logging.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
 		}
 	}
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
-		log.V(util.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
+		log.V(logging.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
 			"namespace", req.Namespace, "name", req.Name, "class", r.IngressClassName)
 		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
 	} else {
-		log.V(util.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
+		log.V(logging.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
 			"class", r.IngressClassName)
 	}
 
@@ -1833,7 +1833,7 @@ func (r *IncubatorV1Alpha1KongServiceFacadeReconciler) Reconcile(ctx context.Con
 	}
 	// if status updates are enabled report the status for the object
 	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
-		log.V(util.DebugLevel).Info("Updating programmed condition status", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Updating programmed condition status", "namespace", req.Namespace, "name", req.Name)
 		configurationStatus := r.DataplaneClient.KubernetesObjectConfigurationStatus(obj)
 		conditions, updateNeeded := ctrlutils.EnsureProgrammedCondition(
 			configurationStatus,
@@ -1845,7 +1845,7 @@ func (r *IncubatorV1Alpha1KongServiceFacadeReconciler) Reconcile(ctx context.Con
 		if updateNeeded {
 			return ctrl.Result{}, r.Status().Update(ctx, obj)
 		}
-		log.V(util.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
 	}
 
 	return ctrl.Result{}, nil
@@ -1954,11 +1954,11 @@ func (r *KongV1Alpha1KongVaultReconciler) Reconcile(ctx context.Context, req ctr
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongVault", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongVault", "namespace", req.Namespace, "name", req.Name)
 
 		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
@@ -1980,16 +1980,16 @@ func (r *KongV1Alpha1KongVaultReconciler) Reconcile(ctx context.Context, req ctr
 			// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
 			// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
 			// if none exists.
-			log.V(util.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
+			log.V(logging.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
 		}
 	}
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
-		log.V(util.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
+		log.V(logging.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
 			"namespace", req.Namespace, "name", req.Name, "class", r.IngressClassName)
 		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
 	} else {
-		log.V(util.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
+		log.V(logging.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
 			"class", r.IngressClassName)
 	}
 
@@ -1999,7 +1999,7 @@ func (r *KongV1Alpha1KongVaultReconciler) Reconcile(ctx context.Context, req ctr
 	}
 	// if status updates are enabled report the status for the object
 	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
-		log.V(util.DebugLevel).Info("Updating programmed condition status", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Updating programmed condition status", "namespace", req.Namespace, "name", req.Name)
 		configurationStatus := r.DataplaneClient.KubernetesObjectConfigurationStatus(obj)
 		conditions, updateNeeded := ctrlutils.EnsureProgrammedCondition(
 			configurationStatus,
@@ -2010,7 +2010,7 @@ func (r *KongV1Alpha1KongVaultReconciler) Reconcile(ctx context.Context, req ctr
 		if updateNeeded {
 			return ctrl.Result{}, r.Status().Update(ctx, obj)
 		}
-		log.V(util.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
 	}
 
 	return ctrl.Result{}, nil
@@ -2119,11 +2119,11 @@ func (r *KongV1Alpha1KongCustomEntityReconciler) Reconcile(ctx context.Context,
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
+	log.V(logging.DebugLevel).Info("Reconciling resource", "namespace", req.Namespace, "name", req.Name)
 
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
-		log.V(util.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongCustomEntity", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Resource is being deleted, its configuration will be removed", "type", "KongCustomEntity", "namespace", req.Namespace, "name", req.Name)
 
 		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
@@ -2145,16 +2145,16 @@ func (r *KongV1Alpha1KongCustomEntityReconciler) Reconcile(ctx context.Context,
 			// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
 			// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
 			// if none exists.
-			log.V(util.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
+			log.V(logging.DebugLevel).Info("Could not retrieve IngressClass", "ingressclass", r.IngressClassName)
 		}
 	}
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
-		log.V(util.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
+		log.V(logging.DebugLevel).Info("Object missing ingress class, ensuring it's removed from configuration",
 			"namespace", req.Namespace, "name", req.Name, "class", r.IngressClassName)
 		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
 	} else {
-		log.V(util.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
+		log.V(logging.DebugLevel).Info("Object has matching ingress class", "namespace", req.Namespace, "name", req.Name,
 			"class", r.IngressClassName)
 	}
 
@@ -2164,7 +2164,7 @@ func (r *KongV1Alpha1KongCustomEntityReconciler) Reconcile(ctx context.Context,
 	}
 	// if status updates are enabled report the status for the object
 	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
-		log.V(util.DebugLevel).Info("Updating programmed condition status", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Updating programmed condition status", "namespace", req.Namespace, "name", req.Name)
 		configurationStatus := r.DataplaneClient.KubernetesObjectConfigurationStatus(obj)
 		conditions, updateNeeded := ctrlutils.EnsureProgrammedCondition(
 			configurationStatus,
@@ -2175,7 +2175,7 @@ func (r *KongV1Alpha1KongCustomEntityReconciler) Reconcile(ctx context.Context,
 		if updateNeeded {
 			return ctrl.Result{}, r.Status().Update(ctx, obj)
 		}
-		log.V(util.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
+		log.V(logging.DebugLevel).Info("Status update not needed", "namespace", req.Namespace, "name", req.Name)
 	}
 
 	return ctrl.Result{}, nil
diff --git a/internal/controllers/crds/dynamic_controller.go b/internal/controllers/crds/dynamic_controller.go
index 1b87c48e5..3a546f38a 100644
--- a/internal/controllers/crds/dynamic_controller.go
+++ b/internal/controllers/crds/dynamic_controller.go
@@ -19,7 +19,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/controllers/utils"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 )
 
 // +kubebuilder:rbac:groups="apiextensions.k8s.io",resources=customresourcedefinitions,verbs=list;watch
@@ -45,7 +45,7 @@ type DynamicCRDController struct {
 
 func (r *DynamicCRDController) SetupWithManager(mgr ctrl.Manager) error {
 	if r.allRequiredCRDsInstalled() {
-		r.Log.V(util.DebugLevel).Info("All required CustomResourceDefinitions are installed, skipping DynamicCRDController set up")
+		r.Log.V(logging.DebugLevel).Info("All required CustomResourceDefinitions are installed, skipping DynamicCRDController set up")
 		return r.setupController(mgr)
 	}
 
@@ -72,21 +72,21 @@ func (r *DynamicCRDController) Reconcile(ctx context.Context, req ctrl.Request)
 	crd := new(apiextensionsv1.CustomResourceDefinition)
 	if err := r.Manager.GetClient().Get(ctx, req.NamespacedName, crd); err != nil {
 		if apierrors.IsNotFound(err) {
-			log.V(util.DebugLevel).Info("Object enqueued no longer exists, skipping", "name", req.Name)
+			log.V(logging.DebugLevel).Info("Object enqueued no longer exists, skipping", "name", req.Name)
 			return ctrl.Result{}, nil
 		}
 		return ctrl.Result{}, err
 	}
-	log.V(util.DebugLevel).Info("Processing CustomResourceDefinition", "name", req.Name)
+	log.V(logging.DebugLevel).Info("Processing CustomResourceDefinition", "name", req.Name)
 
 	if !r.allRequiredCRDsInstalled() {
-		log.V(util.DebugLevel).Info("Still not all required CustomResourceDefinitions are installed, waiting")
+		log.V(logging.DebugLevel).Info("Still not all required CustomResourceDefinitions are installed, waiting")
 		return ctrl.Result{}, nil
 	}
 
 	var startControllerErr error
 	r.startControllerOnce.Do(func() {
-		log.V(util.InfoLevel).Info("All required CustomResourceDefinitions are installed, setting up the controller")
+		log.V(logging.InfoLevel).Info("All required CustomResourceDefinitions are installed, setting up the controller")
 		startControllerErr = r.setupController(r.Manager)
 	})
 	if startControllerErr != nil {
diff --git a/internal/controllers/gateway/gateway_controller.go b/internal/controllers/gateway/gateway_controller.go
index 0972db0c0..413f4d741 100644
--- a/internal/controllers/gateway/gateway_controller.go
+++ b/internal/controllers/gateway/gateway_controller.go
@@ -33,7 +33,7 @@ import (
 	ctrlref "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers/reference"
 	ctrlutils "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers/utils"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 )
 
 // -----------------------------------------------------------------------------
@@ -376,7 +376,7 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
 	log := r.Log.WithValues("GatewayV1Gateway", req.NamespacedName)
 
 	if nn, isSet := r.GatewayNN.Get(); isSet && !r.GatewayNN.MatchesNN(req.NamespacedName) {
-		r.Log.V(util.DebugLevel).Info(
+		r.Log.V(logging.DebugLevel).Info(
 			"The request does not match the specified Gateway and will be skipped.",
 			"gateway", nn,
 			"request", req.String(),
@@ -515,7 +515,7 @@ func (r *GatewayReconciler) reconcileUnmanagedGateway(ctx context.Context, log l
 	debug(log, gateway, "Gathering the gateway publish service") // this will also be done by the validating webhook, this is a fallback
 	var gatewayServices []*corev1.Service
 	for _, ref := range serviceRefs {
-		r.Log.V(util.DebugLevel).Info("Determining service for ref", "ref", ref)
+		r.Log.V(logging.DebugLevel).Info("Determining service for ref", "ref", ref)
 		svc, err := r.determineServiceForGateway(ctx, ref)
 		if err != nil {
 			const annotation = annotations.AnnotationPrefix + annotations.GatewayPublishServiceKey
@@ -687,7 +687,7 @@ func (r *GatewayReconciler) determineServiceForGateway(ctx context.Context, ref
 	// retrieve the service for the kong gateway
 	svc := &corev1.Service{}
 	if name.Name == "" && name.Namespace == "" {
-		r.Log.V(util.DebugLevel).Info("Service not configured, discarding it", "ref", ref)
+		r.Log.V(logging.DebugLevel).Info("Service not configured, discarding it", "ref", ref)
 		return nil, nil
 	}
 	if err := r.Client.Get(ctx, name, svc); err != nil {
diff --git a/internal/controllers/gateway/gatewayclass_controller.go b/internal/controllers/gateway/gatewayclass_controller.go
index d22d30eb4..cb12564ab 100644
--- a/internal/controllers/gateway/gatewayclass_controller.go
+++ b/internal/controllers/gateway/gatewayclass_controller.go
@@ -19,6 +19,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
 )
 
@@ -114,13 +115,13 @@ func (r *GatewayClassReconciler) Reconcile(ctx context.Context, req ctrl.Request
 	gwc := new(gatewayapi.GatewayClass)
 	if err := r.Client.Get(ctx, req.NamespacedName, gwc); err != nil {
 		if apierrors.IsNotFound(err) {
-			log.V(util.DebugLevel).Info("Object enqueued no longer exists, skipping", "name", req.Name)
+			log.V(logging.DebugLevel).Info("Object enqueued no longer exists, skipping", "name", req.Name)
 			return ctrl.Result{}, nil
 		}
 		return ctrl.Result{}, err
 	}
 
-	log.V(util.DebugLevel).Info("Processing gatewayclass", "name", req.Name)
+	log.V(logging.DebugLevel).Info("Processing gatewayclass", "name", req.Name)
 
 	if isGatewayClassControlled(gwc) {
 		alreadyAccepted := util.CheckCondition(
diff --git a/internal/controllers/gateway/route_utils.go b/internal/controllers/gateway/route_utils.go
index cb42bf58f..dbe2aa78b 100644
--- a/internal/controllers/gateway/route_utils.go
+++ b/internal/controllers/gateway/route_utils.go
@@ -19,6 +19,7 @@ import (
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/controllers"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
 )
 
@@ -223,7 +224,7 @@ func getSupportedGatewayForRoute[T gatewayapi.RouteT](
 					listener.Name, route.GetName(), gateway.Name, err,
 				)
 			} else if !ok {
-				listenerLogger.V(util.DebugLevel).Info("Route does not match listener's allowed routes")
+				listenerLogger.V(logging.DebugLevel).Info("Route does not match listener's allowed routes")
 				continue
 			}
 			allowedByAllowedRoutes = true
@@ -232,14 +233,14 @@ func getSupportedGatewayForRoute[T gatewayapi.RouteT](
 			// - Check if a listener status exists with a matching type (via SupportedKinds).
 			// - Check if it matches the requested listener by name (if specified).
 			if err := existsMatchingListenerInStatus(route, listener, gateway.Status.Listeners); err != nil {
-				listenerLogger.V(util.DebugLevel).Info("Listener does not support this route", "reason", err.Error())
+				listenerLogger.V(logging.DebugLevel).Info("Listener does not support this route", "reason", err.Error())
 				continue
 			} else { //nolint:revive
 				allowedBySupportedKinds = true
 			}
 
 			if err := listenerProgrammedInStatus(listener.Name, gateway.Status.Listeners); err != nil {
-				listenerLogger.V(util.DebugLevel).Info("Listener is not ready", "reason", err.Error())
+				listenerLogger.V(logging.DebugLevel).Info("Listener is not ready", "reason", err.Error())
 				continue
 			} else { //nolint:revive
 				listenerReady = true
@@ -248,7 +249,7 @@ func getSupportedGatewayForRoute[T gatewayapi.RouteT](
 			// Check if listener name matches.
 			if parentRef.SectionName != nil {
 				if *parentRef.SectionName != "" && *parentRef.SectionName != listener.Name {
-					listenerLogger.V(util.DebugLevel).Info(
+					listenerLogger.V(logging.DebugLevel).Info(
 						"Listener name does not match parentRef.SectionName",
 						"parentRef_sectionName", parentRef.SectionName,
 					)
@@ -262,7 +263,7 @@ func getSupportedGatewayForRoute[T gatewayapi.RouteT](
 				if *parentRef.Port != listener.Port {
 					// This ParentRef has a port specified and it's different
 					// than current listener's port.
-					listenerLogger.V(util.DebugLevel).Info(
+					listenerLogger.V(logging.DebugLevel).Info(
 						"Listener port does not match parentRef.Port",
 						"listener_port", listener.Port, "parentRef_port", parentRef.Port,
 					)
@@ -273,7 +274,7 @@ func getSupportedGatewayForRoute[T gatewayapi.RouteT](
 
 			// Check if listener protocol matches
 			if !routeTypeMatchesListenerType(route, listener) {
-				listenerLogger.V(util.DebugLevel).Info(
+				listenerLogger.V(logging.DebugLevel).Info(
 					"Route's type does not match listener's type",
 					"route_name", route.GetName(),
 				)
@@ -286,7 +287,7 @@ func getSupportedGatewayForRoute[T gatewayapi.RouteT](
 			} else {
 				condFalse := metav1.ConditionFalse
 				matchingHostname = &condFalse
-				listenerLogger.V(util.DebugLevel).Info("Route's hostname does not match listener's hostname")
+				listenerLogger.V(logging.DebugLevel).Info("Route's hostname does not match listener's hostname")
 				continue
 			}
 
diff --git a/internal/controllers/gateway/utils.go b/internal/controllers/gateway/utils.go
index 032d1a847..45153f5bd 100644
--- a/internal/controllers/gateway/utils.go
+++ b/internal/controllers/gateway/utils.go
@@ -4,7 +4,7 @@ import (
 	"github.com/go-logr/logr"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 )
 
 // -----------------------------------------------------------------------------
@@ -17,7 +17,7 @@ func debug(log logr.Logger, obj client.Object, msg string, keysAndValues ...inte
 		"namespace", obj.GetNamespace(),
 		"name", obj.GetName(),
 	}, keysAndValues...)
-	log.V(util.DebugLevel).Info(msg, keysAndValues...)
+	log.V(logging.DebugLevel).Info(msg, keysAndValues...)
 }
 
 // info is an alias for the longer log.V(util.InfoLevel).Info for convenience.
@@ -26,5 +26,5 @@ func info(log logr.Logger, obj client.Object, msg string, keysAndValues ...inter
 		"namespace", obj.GetNamespace(),
 		"name", obj.GetName(),
 	}, keysAndValues...)
-	log.V(util.InfoLevel).Info(msg, keysAndValues...)
+	log.V(logging.InfoLevel).Info(msg, keysAndValues...)
 }
diff --git a/internal/controllers/reference/indexer.go b/internal/controllers/reference/indexer.go
index 1bdbdc667..afc4ad1b3 100644
--- a/internal/controllers/reference/indexer.go
+++ b/internal/controllers/reference/indexer.go
@@ -8,6 +8,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/controllers"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/manager/scheme"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
 )
@@ -123,7 +124,7 @@ func ObjectReferenceIndexerReferent(obj interface{}) ([]string, error) {
 
 // SetObjectReference adds or updates a reference record between referrer and referent in reference cache.
 func (c CacheIndexers) SetObjectReference(referrer client.Object, referent client.Object) error {
-	c.logger.V(util.DebugLevel).Info("Set reference relation",
+	c.logger.V(logging.DebugLevel).Info("Set reference relation",
 		"referrer_kind", referrer.GetObjectKind().GroupVersionKind().String(),
 		"referrer_namespace", referrer.GetNamespace(),
 		"referrer_name", referrer.GetName(),
@@ -140,7 +141,7 @@ func (c CacheIndexers) SetObjectReference(referrer client.Object, referent clien
 
 // DeleteObjectReference deletes the reference record between referrer and referent from reference cache.
 func (c CacheIndexers) DeleteObjectReference(referrer client.Object, referent client.Object) error {
-	c.logger.V(util.DebugLevel).Info("Delete reference relation",
+	c.logger.V(logging.DebugLevel).Info("Delete reference relation",
 		"referrer_kind", referrer.GetObjectKind().GroupVersionKind().String(),
 		"referrer_namespace", referrer.GetNamespace(),
 		"referrer_name", referrer.GetName(),
@@ -244,7 +245,7 @@ func (c CacheIndexers) DeleteObjectIfNotReferred(obj client.Object, dataplaneCli
 		return err
 	}
 	if !referred {
-		c.logger.V(util.DebugLevel).Info("Delete object from cache because it is no longer referred",
+		c.logger.V(logging.DebugLevel).Info("Delete object from cache because it is no longer referred",
 			"kind", obj.GetObjectKind(),
 			"namespace", obj.GetNamespace(),
 			"name", obj.GetName(),
diff --git a/internal/dataplane/configfetcher/config_fetcher.go b/internal/dataplane/configfetcher/config_fetcher.go
index d36411fb7..16223c8e4 100644
--- a/internal/dataplane/configfetcher/config_fetcher.go
+++ b/internal/dataplane/configfetcher/config_fetcher.go
@@ -14,7 +14,7 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/kongstate"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/sendconfig"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/license"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 )
 
 type LastValidConfigFetcher interface {
@@ -109,7 +109,7 @@ func (cf *DefaultKongLastGoodConfigFetcher) TryFetchingValidConfigFromGateways(
 	logger logr.Logger,
 	gatewayClients []*adminapi.Client,
 ) error {
-	logger.V(util.DebugLevel).Info("Fetching last good configuration from gateway clients", "count", len(gatewayClients))
+	logger.V(logging.DebugLevel).Info("Fetching last good configuration from gateway clients", "count", len(gatewayClients))
 
 	var (
 		goodKongState *kongstate.KongState
@@ -117,7 +117,7 @@ func (cf *DefaultKongLastGoodConfigFetcher) TryFetchingValidConfigFromGateways(
 		clientUsed    *adminapi.Client
 	)
 	for _, client := range gatewayClients {
-		logger.V(util.DebugLevel).Info("Fetching configuration", "url", client.BaseRootURL())
+		logger.V(logging.DebugLevel).Info("Fetching configuration", "url", client.BaseRootURL())
 		rs, err := cf.getKongRawState(ctx, client.AdminAPIClient())
 		if err != nil {
 			errs = errors.Join(errs, err)
@@ -147,7 +147,7 @@ func (cf *DefaultKongLastGoodConfigFetcher) TryFetchingValidConfigFromGateways(
 			goodKongState.FillIDs(logger, cf.workspace)
 		}
 		cf.lastValidState = goodKongState
-		logger.V(util.DebugLevel).Info("Last good configuration fetched from Kong node", "url", clientUsed.BaseRootURL())
+		logger.V(logging.DebugLevel).Info("Last good configuration fetched from Kong node", "url", clientUsed.BaseRootURL())
 	}
 	return errs
 }
diff --git a/internal/dataplane/fallback/fallback.go b/internal/dataplane/fallback/fallback.go
index 4d0b3b0e0..0b9912b8f 100644
--- a/internal/dataplane/fallback/fallback.go
+++ b/internal/dataplane/fallback/fallback.go
@@ -5,8 +5,8 @@ import (
 
 	"github.com/go-logr/logr"
 
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
 )
 
 type CacheGraphProvider interface {
@@ -97,7 +97,7 @@ func (g *Generator) GenerateBackfillingBrokenObjects(
 	}
 
 	if lastValidCacheSnapshot == nil {
-		g.logger.V(util.DebugLevel).Info("No previous valid cache snapshot found, skipping backfilling")
+		g.logger.V(logging.DebugLevel).Info("No previous valid cache snapshot found, skipping backfilling")
 		return fallbackCache, metadataCollector.Metadata(), nil
 	}
 
diff --git a/internal/dataplane/kong_client.go b/internal/dataplane/kong_client.go
index 0641b540b..27834bb3b 100644
--- a/internal/dataplane/kong_client.go
+++ b/internal/dataplane/kong_client.go
@@ -36,6 +36,7 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/sendconfig"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/translator"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/diagnostics"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/metrics"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
@@ -457,7 +458,7 @@ func (c *KongClient) Update(ctx context.Context) error {
 			c.prometheusMetrics.RecordProcessedConfigSnapshotCacheMiss()
 		}
 		if hasNewSnapshotToBeProcessed {
-			c.logger.V(util.DebugLevel).Info("New configuration snapshot detected", "hash", newSnapshotHash)
+			c.logger.V(logging.DebugLevel).Info("New configuration snapshot detected", "hash", newSnapshotHash)
 			c.lastProcessedSnapshotHash = newSnapshotHash
 			c.kongConfigBuilder.UpdateCache(cacheSnapshot)
 		}
@@ -465,22 +466,22 @@ func (c *KongClient) Update(ctx context.Context) error {
 		if allGatewaysAreInSync := lo.EveryBy(c.clientsProvider.GatewayClientsToConfigure(), func(cl *adminapi.Client) bool {
 			return cl.LastCacheStoresHash() == c.lastProcessedSnapshotHash
 		}); allGatewaysAreInSync {
-			c.logger.V(util.DebugLevel).Info("All gateways are in sync; pushing config is not necessary, skipping")
+			c.logger.V(logging.DebugLevel).Info("All gateways are in sync; pushing config is not necessary, skipping")
 			return nil
 		}
 	}
 
-	c.logger.V(util.DebugLevel).Info("Parsing kubernetes objects into data-plane configuration")
+	c.logger.V(logging.DebugLevel).Info("Parsing kubernetes objects into data-plane configuration")
 	parsingResult := c.kongConfigBuilder.BuildKongConfig()
 	if failuresCount := len(parsingResult.TranslationFailures); failuresCount > 0 {
 		c.prometheusMetrics.RecordTranslationFailure()
 		c.prometheusMetrics.RecordTranslationBrokenResources(failuresCount)
 		c.recordResourceFailureEvents(parsingResult.TranslationFailures, KongConfigurationTranslationFailedEventReason)
-		c.logger.V(util.DebugLevel).Info("Translation failures occurred when building data-plane configuration", "count", failuresCount)
+		c.logger.V(logging.DebugLevel).Info("Translation failures occurred when building data-plane configuration", "count", failuresCount)
 	} else {
 		c.prometheusMetrics.RecordTranslationSuccess()
 		c.prometheusMetrics.RecordTranslationBrokenResources(0)
-		c.logger.V(util.DebugLevel).Info("Successfully built data-plane configuration")
+		c.logger.V(logging.DebugLevel).Info("Successfully built data-plane configuration")
 	}
 
 	const isFallback = false
@@ -519,11 +520,11 @@ func (c *KongClient) Update(ctx context.Context) error {
 		// if the configuration SHAs that have just been pushed are different than
 		// what's been previously pushed.
 		if !slices.Equal(shas, c.SHAs) {
-			c.logger.V(util.DebugLevel).Info("Triggering report for configured Kubernetes objects", "count",
+			c.logger.V(logging.DebugLevel).Info("Triggering report for configured Kubernetes objects", "count",
 				len(parsingResult.ConfiguredKubernetesObjects))
 			c.triggerKubernetesObjectReport(parsingResult.ConfiguredKubernetesObjects, parsingResult.TranslationFailures)
 		} else {
-			c.logger.V(util.DebugLevel).Info("No configuration change; resource status update not necessary, skipping")
+			c.logger.V(logging.DebugLevel).Info("No configuration change; resource status update not necessary, skipping")
 		}
 	}
 	return nil
@@ -533,7 +534,7 @@ func (c *KongClient) Update(ctx context.Context) error {
 // feature gate is enabled and the `--enable-last-valid-config-fallback` flag is set.
 func (c *KongClient) maybePreserveTheLastValidConfigCache(lastValidCache store.CacheStores) {
 	if c.kongConfig.FallbackConfiguration && c.kongConfig.UseLastValidConfigForFallback {
-		c.logger.V(util.DebugLevel).Info("Preserving the last valid configuration cache")
+		c.logger.V(logging.DebugLevel).Info("Preserving the last valid configuration cache")
 		c.lastValidCacheSnapshot = &lastValidCache
 	}
 }
@@ -557,7 +558,7 @@ func (c *KongClient) maybeTryRecoveringFromGatewaysSyncError(
 	// If the error is not of the expected UpdateError type, we should log it and skip the recovery.
 	updateErr := sendconfig.UpdateError{}
 	if !errors.As(gatewaysSyncErr, &updateErr) {
-		c.logger.V(util.DebugLevel).Info("Skipping recovery from gateways sync error - not enough details to recover",
+		c.logger.V(logging.DebugLevel).Info("Skipping recovery from gateways sync error - not enough details to recover",
 			"error", gatewaysSyncErr)
 		return nil
 	}
@@ -583,7 +584,7 @@ func (c *KongClient) maybeTryRecoveringFromGatewaysSyncError(
 		if _, fallbackSyncErr := c.sendOutToGatewayClients(ctx, state, c.kongConfig, isFallback); fallbackSyncErr != nil {
 			return errors.Join(gatewaysSyncErr, fallbackSyncErr)
 		}
-		c.logger.V(util.DebugLevel).Info("Due to errors in the current config, the last valid config has been pushed to Gateways")
+		c.logger.V(logging.DebugLevel).Info("Due to errors in the current config, the last valid config has been pushed to Gateways")
 	}
 	return nil
 }
@@ -613,7 +614,7 @@ func (c *KongClient) tryRecoveringWithFallbackConfiguration(
 		c.recordResourceFailureEvents(fallbackParsingResult.TranslationFailures, FallbackKongConfigurationTranslationFailedEventReason)
 		c.prometheusMetrics.RecordFallbackTranslationBrokenResources(failuresCount)
 		c.prometheusMetrics.RecordFallbackTranslationFailure()
-		c.logger.V(util.DebugLevel).Info("Translation failures occurred when building fallback data-plane configuration", "count", failuresCount)
+		c.logger.V(logging.DebugLevel).Info("Translation failures occurred when building fallback data-plane configuration", "count", failuresCount)
 	} else {
 		c.prometheusMetrics.RecordFallbackTranslationBrokenResources(0)
 		c.prometheusMetrics.RecordFallbackTranslationSuccess()
@@ -690,7 +691,7 @@ func (c *KongClient) sendOutToGatewayClients(
 
 	gatewayClientsToConfigure := c.clientsProvider.GatewayClientsToConfigure()
 	configureGatewayClientURLs := lo.Map(gatewayClientsToConfigure, func(cl *adminapi.Client, _ int) string { return cl.BaseRootURL() })
-	c.logger.V(util.DebugLevel).Info("Sending configuration to gateway clients", "urls", configureGatewayClientURLs)
+	c.logger.V(logging.DebugLevel).Info("Sending configuration to gateway clients", "urls", configureGatewayClientURLs)
 
 	shas, err := iter.MapErr(gatewayClientsToConfigure, func(client **adminapi.Client) (string, error) {
 		return c.sendToClient(ctx, *client, s, config, isFallback)
@@ -916,7 +917,7 @@ func prepareSendDiagnosticFn(
 			Config:          *config,
 			RawResponseBody: rawResponseBody,
 		}:
-			logger.V(util.DebugLevel).Info("Shipping config to diagnostic server")
+			logger.V(logging.DebugLevel).Info("Shipping config to diagnostic server")
 		default:
 			logger.Error(nil, "Config diagnostic buffer full, dropping diagnostic config")
 		}
@@ -1037,11 +1038,11 @@ func (c *KongClient) recordApplyConfigurationEvents(err error, rootURL string, i
 func (c *KongClient) updateConfigStatus(ctx context.Context, configStatus clients.ConfigStatus) {
 	if c.currentConfigStatus == configStatus {
 		// No change in config status, nothing to do.
-		c.logger.V(util.DebugLevel).Info("No change in config status, not notifying")
+		c.logger.V(logging.DebugLevel).Info("No change in config status, not notifying")
 		return
 	}
 
-	c.logger.V(util.DebugLevel).Info("Config status changed, notifying", "configStatus", configStatus)
+	c.logger.V(logging.DebugLevel).Info("Config status changed, notifying", "configStatus", configStatus)
 	c.currentConfigStatus = configStatus
 	c.configStatusNotifier.NotifyConfigStatus(ctx, configStatus)
 }
@@ -1056,7 +1057,7 @@ func (c *KongClient) logFallbackCacheMetadata(metadata fallback.GeneratedCacheMe
 		causingObjects := lo.Map(excluded.CausingObjects, func(causing fallback.ObjectHash, _ int) string {
 			return causing.String()
 		})
-		log.V(util.DebugLevel).Info("Excluded object from fallback cache",
+		log.V(logging.DebugLevel).Info("Excluded object from fallback cache",
 			"kind", gvk.Kind,
 			"group", gvk.Group,
 			"namespace", obj.GetNamespace(),
@@ -1072,7 +1073,7 @@ func (c *KongClient) logFallbackCacheMetadata(metadata fallback.GeneratedCacheMe
 		causingObjects := lo.Map(backfilled.CausingObjects, func(causing fallback.ObjectHash, _ int) string {
 			return causing.String()
 		})
-		log.V(util.DebugLevel).Info("Backfilled object in fallback cache",
+		log.V(logging.DebugLevel).Info("Backfilled object in fallback cache",
 			"kind", gvk.Kind,
 			"group", gvk.Group,
 			"namespace", obj.GetNamespace(),
@@ -1086,7 +1087,7 @@ func (c *KongClient) maybeSendFallbackConfigDiagnostics(ctx context.Context, gen
 	if ch := c.diagnostic.FallbackCacheMetadata; ch != nil {
 		select {
 		case ch <- generatedCacheMetadata:
-			c.logger.V(util.DebugLevel).Info("Shipping fallback cache metadata to diagnostics server")
+			c.logger.V(logging.DebugLevel).Info("Shipping fallback cache metadata to diagnostics server")
 		case <-ctx.Done():
 			return ctx.Err()
 		default:
diff --git a/internal/dataplane/kongstate/kongstate.go b/internal/dataplane/kongstate/kongstate.go
index 404e53674..ef162e686 100644
--- a/internal/dataplane/kongstate/kongstate.go
+++ b/internal/dataplane/kongstate/kongstate.go
@@ -21,9 +21,9 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/annotations"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/failures"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
-	kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1"
 	kongv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1alpha1"
 	kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1beta1"
 )
@@ -353,18 +353,18 @@ func (ks *KongState) getPluginRelations(cacheStore store.Storer, log logr.Logger
 		RouteRelation         entityRelationType = iota
 		ServiceRelation       entityRelationType = iota
 	)
-	addRelation := func(referer client.Object, plugin annotations.NamespacedKongPlugin, identifier string, t entityRelationType) {
+	addRelation := func(referrer client.Object, plugin annotations.NamespacedKongPlugin, identifier string, t entityRelationType) {
 		// There are 2 types of KongPlugin references: local and remote.
-		// A local reference is one where the KongPlugin is in the same namespace as the referer.
+		// A local reference is one where the KongPlugin is in the same namespace as the referrer.
 		// A remote reference is one where the KongPlugin is in a different namespace.
 		// By default a KongPlugin is considered local.
 		// If the plugin has a namespace specified, it is considered remote.
 		//
-		// The referer is the entity that the KongPlugin is associated with.
+		// The referrer is the entity that the KongPlugin is associated with.
 		//
 		// Code in buildPlugins() will combine plugin associations into
 		// multi-entity plugins within the local namespace
-		namespace, err := extractReferredPluginNamespace(cacheStore, referer, plugin)
+		namespace, err := extractReferredPluginNamespace(log, cacheStore, referrer, plugin)
 		if err != nil {
 			log.Error(err, "could not bind requested plugin", "plugin", plugin.Name, "namespace", plugin.Namespace)
 			return
@@ -400,8 +400,11 @@ func (ks *KongState) getPluginRelations(cacheStore store.Storer, log logr.Logger
 			ingress := ks.Services[i].Routes[j].Ingress
 			pluginList := annotations.ExtractNamespacedKongPluginsFromAnnotations(ingress.Annotations)
 			for _, plugin := range pluginList {
-				// pretend we have a full Ingress struct for reference checks
+				// pretend we have a full Ingress struct for reference checks.
+				// REVIEW: we only need an object to carry type meta and object meta here, maybe we should create some other types of virtual object here?
 				virtualIngress := netv1.Ingress{
+					// Fill the actual type of the object for reference checks.
+					TypeMeta: util.TypeMetaFromGVK(ingress.GroupVersionKind),
 					ObjectMeta: metav1.ObjectMeta{
 						Namespace: ingress.Namespace,
 						Name:      ingress.Name,
@@ -430,38 +433,59 @@ func (ks *KongState) getPluginRelations(cacheStore store.Storer, log logr.Logger
 }
 
 type pluginReference struct {
-	Referer   client.Object
+	Referrer  client.Object
 	Namespace string
 	Name      string
 }
 
-func isRemotePluginReferenceAllowed(s store.Storer, r pluginReference) error {
+func isRemotePluginReferenceAllowed(log logr.Logger, s store.Storer, r pluginReference) error {
 	// remote plugin. considered part of this namespace if a suitable ReferenceGrant exists
 	grants, err := s.ListReferenceGrants()
 	if err != nil {
 		return fmt.Errorf("could not retrieve ReferenceGrants from store when building plugin relations map: %w", err)
 	}
-	allowed := gatewayapi.GetPermittedForReferenceGrantFrom(gatewayapi.ReferenceGrantFrom{
-		Group:     gatewayapi.Group(r.Referer.GetObjectKind().GroupVersionKind().Group),
-		Kind:      gatewayapi.Kind(r.Referer.GetObjectKind().GroupVersionKind().Kind),
-		Namespace: gatewayapi.Namespace(r.Referer.GetNamespace()),
-	}, grants)
+	allowed := gatewayapi.GetPermittedForReferenceGrantFrom(
+		log,
+		gatewayapi.ReferenceGrantFrom{
+			Group:     gatewayapi.Group(r.Referrer.GetObjectKind().GroupVersionKind().Group),
+			Kind:      gatewayapi.Kind(r.Referrer.GetObjectKind().GroupVersionKind().Kind),
+			Namespace: gatewayapi.Namespace(r.Referrer.GetNamespace()),
+		},
+		grants,
+	)
+
+	// TODO https://github.com/Kong/kubernetes-ingress-controller/issues/6000
+	// Our reference checking code wasn't originally designed to handle multiple object types and relationships and
+	// wasn't designed with much guidance for future usage. It expects something akin to the BackendRef section of an
+	// HTTPRoute or similar, since that's what it was originally designed for. A future simplified model should probably
+	// work with source and target client.Object resources derived from the particular resources' reference specs, as
+	// those reference formats aren't necessarily consistent. We should possibly push for a standard upstream utility as
+	// part of https://github.com/kubernetes/enhancements/issues/3766 if it proceeds further, as this is can be difficult
+	// to wrap your head around when deep in the code that's checking an individual use case. A standard model for "these
+	// are the inputs and outputs for a reference grant check, derive them from your spec" should help avoid inconsistency
+	// in check implementation.
 
 	// we don't have a full plugin resource here for the grant checker, so we build a fake one with the correct
-	// name and namespace
-	virtualReference := gatewayapi.PluginLabelReference{
-		Namespace: lo.ToPtr(r.Referer.GetNamespace()),
+	// name and namespace.
+	pluginReference := gatewayapi.PluginLabelReference{
+		Namespace: &r.Namespace,
 		Name:      r.Name,
 	}
-	virtualPlugin := &kongv1.KongPlugin{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: r.Namespace,
-			Name:      r.Name,
-		},
-	}
-	if !gatewayapi.NewRefCheckerForKongPlugin(virtualPlugin, virtualReference).IsRefAllowedByGrant(allowed) {
+
+	// Because we should check whether it is allowed to refer FROM the referrer TO the plugin here,
+	// we should put the referrer on the "target" and the plugin on the "backendRef".
+
+	log.V(logging.TraceLevel).Info("requested grant to plugins",
+		"from-namespace", r.Referrer.GetNamespace(),
+		"from-group", r.Referrer.GetObjectKind().GroupVersionKind().Group,
+		"from-kind", r.Referrer.GetObjectKind().GroupVersionKind().Kind,
+		"to-namespace", r.Namespace,
+		"to-name", r.Name,
+	)
+
+	if !gatewayapi.NewRefCheckerForKongPlugin(log, r.Referrer, pluginReference).IsRefAllowedByGrant(allowed) {
 		return fmt.Errorf("no grant found for %s in %s to plugin %s in %s requested remote KongPlugin bind",
-			r.Referer.GetObjectKind().GroupVersionKind().Kind, r.Referer.GetNamespace(), r.Name, r.Namespace)
+			r.Referrer.GetObjectKind().GroupVersionKind().Kind, r.Referrer.GetNamespace(), r.Name, r.Namespace)
 	}
 	return nil
 }
@@ -799,7 +823,7 @@ func (ks *KongState) fillCustomEntityForeignFields(
 	if !ok {
 		return
 	}
-	logger.V(util.DebugLevel).Info("fetch references via plugin", "plugin_key", pluginKey)
+	logger.V(logging.DebugLevel).Info("fetch references via plugin", "plugin_key", pluginKey)
 
 	// Traverse through the fields of the entity and fill the "foreign" fields with IDs of referring entities.
 	// Note: this procedure will make referred services'/routes'/consumers' ID to be filled.
@@ -817,7 +841,7 @@ func (ks *KongState) fillCustomEntityForeignFields(
 				parsedEntity[fieldName] = map[string]interface{}{
 					"id": serviceIDs[0],
 				}
-				logger.V(util.DebugLevel).Info("added ref to service", "service_id", serviceIDs[0])
+				logger.V(logging.DebugLevel).Info("added ref to service", "service_id", serviceIDs[0])
 			}
 		case string(kong.EntityTypeRoutes):
 			routeIDs := lo.FilterMap(rels.Routes, func(r *Route, _ int) (string, bool) {
@@ -830,7 +854,7 @@ func (ks *KongState) fillCustomEntityForeignFields(
 				parsedEntity[fieldName] = map[string]interface{}{
 					"id": routeIDs[0],
 				}
-				logger.V(util.DebugLevel).Info("added ref to route", "route_id", routeIDs[0])
+				logger.V(logging.DebugLevel).Info("added ref to route", "route_id", routeIDs[0])
 			}
 		case string(kong.EntityTypeConsumers):
 			consumerIDs := lo.FilterMap(rels.Consumers, func(c *Consumer, _ int) (string, bool) {
@@ -843,7 +867,7 @@ func (ks *KongState) fillCustomEntityForeignFields(
 				parsedEntity[fieldName] = map[string]interface{}{
 					"id": consumerIDs[0],
 				}
-				logger.V(util.DebugLevel).Info("added ref to consumer", "consumer_id", consumerIDs[0])
+				logger.V(logging.DebugLevel).Info("added ref to consumer", "consumer_id", consumerIDs[0])
 			}
 		}
 	}
@@ -896,8 +920,8 @@ func (ks *KongState) getPluginRelatedEntitiesRef(cacheStore store.Storer, log lo
 		RelatedEntities:      map[string]RelatedEntitiesRef{},
 		RouteAttachedService: map[string]*Service{},
 	}
-	addRelation := func(referer client.Object, plugin annotations.NamespacedKongPlugin, entity any) {
-		namespace, err := extractReferredPluginNamespace(cacheStore, referer, plugin)
+	addRelation := func(referrer client.Object, plugin annotations.NamespacedKongPlugin, entity any) {
+		namespace, err := extractReferredPluginNamespace(log, cacheStore, referrer, plugin)
 		if err != nil {
 			log.Error(err, "could not bind requested plugin", "plugin", plugin.Name, "namespace", plugin.Namespace)
 			return
@@ -932,6 +956,8 @@ func (ks *KongState) getPluginRelatedEntitiesRef(cacheStore store.Storer, log lo
 			for _, plugin := range pluginList {
 				// Pretend we have a full Ingress struct for reference checks.
 				virtualIngress := netv1.Ingress{
+					// Fill the actual type of the object for reference checks.
+					TypeMeta: util.TypeMetaFromGVK(ingress.GroupVersionKind),
 					ObjectMeta: metav1.ObjectMeta{
 						Namespace: ingress.Namespace,
 						Name:      ingress.Name,
@@ -956,24 +982,25 @@ func (ks *KongState) getPluginRelatedEntitiesRef(cacheStore store.Storer, log lo
 }
 
 func extractReferredPluginNamespace(
-	cacheStore store.Storer, referer client.Object, plugin annotations.NamespacedKongPlugin,
+	log logr.Logger, cacheStore store.Storer, referrer client.Object, plugin annotations.NamespacedKongPlugin,
 ) (string, error) {
 	// There are 2 types of KongPlugin references: local and remote.
-	// A local reference is one where the KongPlugin is in the same namespace as the referer.
+	// A local reference is one where the KongPlugin is in the same namespace as the referrer.
 	// A remote reference is one where the KongPlugin is in a different namespace.
 	// By default a KongPlugin is considered local.
 	// If the plugin has a namespace specified, it is considered remote.
 	//
-	// The referer is the entity that the KongPlugin is associated with.
+	// The referrer is the entity that the KongPlugin is associated with.
 	if plugin.Namespace == "" {
-		return referer.GetNamespace(), nil
+		return referrer.GetNamespace(), nil
 	}
 
 	// remote KongPlugin, permitted if ReferenceGrant allows.
 	err := isRemotePluginReferenceAllowed(
+		log,
 		cacheStore,
 		pluginReference{
-			Referer:   referer,
+			Referrer:  referrer,
 			Namespace: plugin.Namespace,
 			Name:      plugin.Name,
 		},
diff --git a/internal/dataplane/sendconfig/backoff_strategy.go b/internal/dataplane/sendconfig/backoff_strategy.go
index 96dfc0121..ce09dbd4f 100644
--- a/internal/dataplane/sendconfig/backoff_strategy.go
+++ b/internal/dataplane/sendconfig/backoff_strategy.go
@@ -7,8 +7,8 @@ import (
 	"github.com/go-logr/logr"
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/adminapi"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/metrics"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
 )
 
 type UpdateSkippedDueToBackoffStrategyError struct {
@@ -54,7 +54,7 @@ func (s UpdateStrategyWithBackoff) Update(ctx context.Context, targetContent Con
 
 	err = s.decorated.Update(ctx, targetContent)
 	if err != nil {
-		s.logger.V(util.DebugLevel).Info("Update failed, registering it for backoff strategy", "reason", err.Error())
+		s.logger.V(logging.DebugLevel).Info("Update failed, registering it for backoff strategy", "reason", err.Error())
 		s.backoffStrategy.RegisterUpdateFailure(err, targetContent.Hash)
 		return err
 	}
diff --git a/internal/dataplane/sendconfig/dbmode.go b/internal/dataplane/sendconfig/dbmode.go
index 5d0e35bac..11e9b8a8f 100644
--- a/internal/dataplane/sendconfig/dbmode.go
+++ b/internal/dataplane/sendconfig/dbmode.go
@@ -17,8 +17,8 @@ import (
 	"github.com/kong/go-kong/kong"
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/deckerrors"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/metrics"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
 )
 
 // UpdateStrategyDBMode implements the UpdateStrategy interface. It updates Kong's data-plane
@@ -123,7 +123,7 @@ func (s *UpdateStrategyDBMode) HandleEvents(ctx context.Context, events chan dif
 		select {
 		case event := <-events:
 			if event.Error == nil {
-				s.logger.V(util.DebugLevel).Info("updated gateway entity", "action", event.Action, "kind", event.Entity.Kind, "name", event.Entity.Name)
+				s.logger.V(logging.DebugLevel).Info("updated gateway entity", "action", event.Action, "kind", event.Entity.Kind, "name", event.Entity.Name)
 			} else {
 				s.logger.Error(event.Error, "failed updating gateway entity", "action", event.Action, "kind", event.Entity.Kind, "name", event.Entity.Name)
 				parsed, err := resourceErrorFromEntityAction(event)
diff --git a/internal/dataplane/sendconfig/inmemory.go b/internal/dataplane/sendconfig/inmemory.go
index 45aec07d5..df35ca125 100644
--- a/internal/dataplane/sendconfig/inmemory.go
+++ b/internal/dataplane/sendconfig/inmemory.go
@@ -13,8 +13,8 @@ import (
 	"github.com/kong/go-database-reconciler/pkg/file"
 	"github.com/kong/go-kong/kong"
 
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/metrics"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
 )
 
 type ConfigService interface {
@@ -66,7 +66,7 @@ func (s UpdateStrategyInMemory) Update(ctx context.Context, targetState ContentW
 		}
 		for entityType, entities := range targetState.CustomEntities {
 			unmarshaledConfig[entityType] = entities
-			s.logger.V(util.DebugLevel).Info("Filled custom entities", "entity_type", entityType)
+			s.logger.V(logging.DebugLevel).Info("Filled custom entities", "entity_type", entityType)
 		}
 		config, err = json.Marshal(unmarshaledConfig)
 		if err != nil {
diff --git a/internal/dataplane/sendconfig/inmemory_error_handling.go b/internal/dataplane/sendconfig/inmemory_error_handling.go
index 169232f56..d956943a1 100644
--- a/internal/dataplane/sendconfig/inmemory_error_handling.go
+++ b/internal/dataplane/sendconfig/inmemory_error_handling.go
@@ -11,6 +11,7 @@ import (
 	k8stypes "k8s.io/apimachinery/pkg/types"
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/failures"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
 	kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1"
 	kongv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1alpha1"
@@ -222,7 +223,7 @@ func resourceErrorsToResourceFailures(resourceErrors []ResourceError, logger log
 			},
 		}
 		for problemSource, problem := range ee.Problems {
-			logger.V(util.DebugLevel).Info("Adding failure", "resource_name", ee.Name, "source", problemSource, "problem", problem)
+			logger.V(logging.DebugLevel).Info("Adding failure", "resource_name", ee.Name, "source", problemSource, "problem", problem)
 			resourceFailure, failureCreateErr := failures.NewResourceFailure(
 				fmt.Sprintf("invalid %s: %s", problemSource, problem),
 				&obj,
diff --git a/internal/dataplane/sendconfig/sendconfig.go b/internal/dataplane/sendconfig/sendconfig.go
index adf114f31..719623fd8 100644
--- a/internal/dataplane/sendconfig/sendconfig.go
+++ b/internal/dataplane/sendconfig/sendconfig.go
@@ -11,6 +11,7 @@ import (
 	"github.com/kong/go-kong/kong"
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/deckgen"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/metrics"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
@@ -63,9 +64,9 @@ func PerformUpdate(
 		}
 		if !configurationChanged {
 			if client.IsKonnect() {
-				logger.V(util.DebugLevel).Info("No configuration change, skipping sync to Konnect")
+				logger.V(logging.DebugLevel).Info("No configuration change, skipping sync to Konnect")
 			} else {
-				logger.V(util.DebugLevel).Info("No configuration change, skipping sync to Kong")
+				logger.V(logging.DebugLevel).Info("No configuration change, skipping sync to Kong")
 			}
 			return oldSHA, nil
 		}
@@ -105,9 +106,9 @@ func PerformUpdate(
 	}
 
 	if client.IsKonnect() {
-		logger.V(util.InfoLevel).Info("Successfully synced configuration to Konnect")
+		logger.V(logging.InfoLevel).Info("Successfully synced configuration to Konnect")
 	} else {
-		logger.V(util.InfoLevel).Info("Successfully synced configuration to Kong")
+		logger.V(logging.InfoLevel).Info("Successfully synced configuration to Kong")
 	}
 
 	return newSHA, nil
diff --git a/internal/dataplane/translator/backendref.go b/internal/dataplane/translator/backendref.go
index 781fb9c2a..770b14c52 100644
--- a/internal/dataplane/translator/backendref.go
+++ b/internal/dataplane/translator/backendref.go
@@ -65,7 +65,7 @@ func backendRefsToKongStateBackends(
 		}
 
 		if !util.IsBackendRefGroupKindSupported(backendRef.Group, backendRef.Kind) ||
-			!gatewayapi.NewRefCheckerForRoute(route, backendRef).IsRefAllowedByGrant(allowed) {
+			!gatewayapi.NewRefCheckerForRoute(logger, route, backendRef).IsRefAllowedByGrant(allowed) {
 			// we log impermissible refs rather than failing the entire rule. while we cannot actually route to
 			// these, we do not want a single impermissible ref to take the entire rule offline. in the case of edits,
 			// failing the entire rule could potentially delete routes that were previously online and in use, and
diff --git a/internal/dataplane/translator/ingressrules.go b/internal/dataplane/translator/ingressrules.go
index 16cb034aa..93016e21d 100644
--- a/internal/dataplane/translator/ingressrules.go
+++ b/internal/dataplane/translator/ingressrules.go
@@ -16,6 +16,7 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/annotations"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/failures"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/kongstate"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
 )
@@ -158,7 +159,7 @@ func (ir *ingressRules) generateKongServiceTags(
 	// Service doesn't actually exist. attempting to generate tags for that Service would trigger a panic.
 	// The translator should discard this invalid route later, but this adds a placeholder value in case it doesn't.
 	// If you encounter an actual config where a service has these tags, something strange has happened.
-	logger.V(util.DebugLevel).Info("Service has zero k8sServices backends, cannot generate tags for it properly",
+	logger.V(logging.DebugLevel).Info("Service has zero k8sServices backends, cannot generate tags for it properly",
 		"service", *service.Name)
 	return kong.StringSlice(
 		util.K8sNameTagPrefix+"UNKNOWN",
diff --git a/internal/dataplane/translator/translate_certs.go b/internal/dataplane/translator/translate_certs.go
index 47c0254ff..4c2fcf489 100644
--- a/internal/dataplane/translator/translate_certs.go
+++ b/internal/dataplane/translator/translate_certs.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/kongstate"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
 )
 
@@ -62,7 +63,7 @@ func (t *Translator) getGatewayCerts() []certWrapper {
 		for _, listener := range gateway.Spec.Listeners {
 			status, ok := statuses[listener.Name]
 			if !ok {
-				logger.V(util.DebugLevel).Info("Listener missing status information",
+				logger.V(logging.DebugLevel).Info("Listener missing status information",
 					"gateway", gateway.Name,
 					"listener", listener.Name,
 					"listener_protocol", listener.Protocol,
diff --git a/internal/dataplane/translator/translate_upstreams.go b/internal/dataplane/translator/translate_upstreams.go
index 0b4cc62ea..976ca7ee2 100644
--- a/internal/dataplane/translator/translate_upstreams.go
+++ b/internal/dataplane/translator/translate_upstreams.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/annotations"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/kongstate"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
 	kongv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1alpha1"
@@ -79,7 +80,7 @@ func (t *Translator) getUpstreams(serviceMap map[string]kongstate.Service) ([]ko
 				newTargets := getServiceEndpoints(t.logger, t.storer, k8sService, port)
 
 				if len(newTargets) == 0 {
-					t.logger.V(util.InfoLevel).Info("No targets could be found for kubernetes service",
+					t.logger.V(logging.InfoLevel).Info("No targets could be found for kubernetes service",
 						"namespace", k8sService.Namespace, "name", k8sService.Name, "kong_service", *service.Name)
 				}
 
@@ -117,7 +118,7 @@ func (t *Translator) getUpstreams(serviceMap map[string]kongstate.Service) ([]ko
 			targets := lo.Values(targetMap)
 			// warn if an upstream was created with 0 targets
 			if len(targets) == 0 {
-				t.logger.V(util.InfoLevel).Info("No targets found to create upstream", "service_name", *service.Name)
+				t.logger.V(logging.InfoLevel).Info("No targets found to create upstream", "service_name", *service.Name)
 			}
 
 			// define the upstream including all the newly populated targets
@@ -207,7 +208,7 @@ func getServiceEndpoints(
 	var isSvcUpstream bool
 	ingressClassParameters, err := getIngressClassParametersOrDefault(s)
 	if err != nil {
-		logger.V(util.DebugLevel).Info("Unable to retrieve IngressClassParameters", "error", err)
+		logger.V(logging.DebugLevel).Info("Unable to retrieve IngressClassParameters", "error", err)
 	} else {
 		isSvcUpstream = ingressClassParameters.ServiceUpstream
 	}
@@ -219,7 +220,7 @@ func getServiceEndpoints(
 		endpoints = append(endpoints, newEndpoints...)
 	}
 	if len(endpoints) == 0 {
-		logger.V(util.DebugLevel).Info("No active endpoints")
+		logger.V(logging.DebugLevel).Info("No active endpoints")
 	}
 
 	return targetsForEndpoints(endpoints)
@@ -277,7 +278,7 @@ func getEndpoints(
 
 	// ExternalName services
 	if service.Spec.Type == corev1.ServiceTypeExternalName {
-		logger.V(util.DebugLevel).Info("Found service of type=ExternalName")
+		logger.V(logging.DebugLevel).Info("Found service of type=ExternalName")
 		return []util.Endpoint{
 			{
 				Address: service.Spec.ExternalName,
@@ -286,13 +287,13 @@ func getEndpoints(
 		}
 	}
 
-	logger.V(util.DebugLevel).Info("Fetching EndpointSlices")
+	logger.V(logging.DebugLevel).Info("Fetching EndpointSlices")
 	endpointSlices, err := getEndpointSlices(service.Namespace, service.Name)
 	if err != nil {
 		logger.Error(err, "Error fetching EndpointSlices")
 		return []util.Endpoint{}
 	}
-	logger.V(util.DebugLevel).Info("Fetched EndpointSlices", "count", len(endpointSlices))
+	logger.V(logging.DebugLevel).Info("Fetched EndpointSlices", "count", len(endpointSlices))
 
 	// Avoid duplicated upstream servers when the service contains
 	// multiple port definitions sharing the same target port.
@@ -327,7 +328,7 @@ func getEndpoints(
 			}
 		}
 	}
-	logger.V(util.DebugLevel).Info("Found endpoints", "endpoints", upstreamServers)
+	logger.V(logging.DebugLevel).Info("Found endpoints", "endpoints", upstreamServers)
 	return upstreamServers
 }
 
diff --git a/internal/dataplane/translator/translate_utils.go b/internal/dataplane/translator/translate_utils.go
index 6a5449885..c9e7c8793 100644
--- a/internal/dataplane/translator/translate_utils.go
+++ b/internal/dataplane/translator/translate_utils.go
@@ -2,7 +2,6 @@ package translator
 
 import (
 	"fmt"
-	"reflect"
 
 	"github.com/go-logr/logr"
 	"github.com/kong/go-kong/kong"
@@ -42,29 +41,6 @@ func convertGatewayMatchHeadersToKongRouteMatchHeaders(headers []gatewayapi.HTTP
 	return convertedHeaders, nil
 }
 
-// GetPermittedForReferenceGrantFrom takes a ReferenceGrant From (a namespace, group, and kind) and returns a map
-// from a namespace to a slice of ReferenceGrant Tos. When a To is included in the slice, the key namespace has a
-// ReferenceGrant with those Tos and the input From.
-func GetPermittedForReferenceGrantFrom(
-	from gatewayapi.ReferenceGrantFrom,
-	grants []*gatewayapi.ReferenceGrant,
-) map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo {
-	allowed := make(map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo)
-	// loop over all From values in all grants. if we find a match, add all Tos to the list of Tos allowed for the
-	// grant namespace. this technically could add duplicate copies of the Tos if there are duplicate Froms (it makes
-	// no sense to add them, but it's allowed), but duplicate Tos are harmless (we only care about having at least one
-	// matching To when checking if a ReferenceGrant allows a reference)
-	for _, grant := range grants {
-		for _, otherFrom := range grant.Spec.From {
-			if reflect.DeepEqual(from, otherFrom) {
-				allowed[gatewayapi.Namespace(grant.ObjectMeta.Namespace)] = append(allowed[gatewayapi.Namespace(grant.ObjectMeta.Namespace)], grant.Spec.To...)
-			}
-		}
-	}
-
-	return allowed
-}
-
 // generateKongServiceFromBackendRefWithName translates backendRefs into a Kong service for use with the
 // rules generated from a Gateway APIs route. The service name is provided by the caller.
 func generateKongServiceFromBackendRefWithName(
@@ -82,11 +58,15 @@ func generateKongServiceFromBackendRefWithName(
 	if err != nil {
 		return kongstate.Service{}, fmt.Errorf("could not retrieve ReferenceGrants for %s: %w", objName, err)
 	}
-	allowed := GetPermittedForReferenceGrantFrom(gatewayapi.ReferenceGrantFrom{
-		Group:     gatewayapi.Group(route.GetObjectKind().GroupVersionKind().Group),
-		Kind:      gatewayapi.Kind(route.GetObjectKind().GroupVersionKind().Kind),
-		Namespace: gatewayapi.Namespace(route.GetNamespace()),
-	}, grants)
+	allowed := gatewayapi.GetPermittedForReferenceGrantFrom(
+		logger,
+		gatewayapi.ReferenceGrantFrom{
+			Group:     gatewayapi.Group(route.GetObjectKind().GroupVersionKind().Group),
+			Kind:      gatewayapi.Kind(route.GetObjectKind().GroupVersionKind().Kind),
+			Namespace: gatewayapi.Namespace(route.GetNamespace()),
+		},
+		grants,
+	)
 
 	backends := backendRefsToKongStateBackends(logger, storer, route, backendRefs, allowed)
 
diff --git a/internal/diagnostics/server.go b/internal/diagnostics/server.go
index e642a3aef..00d7d8a6d 100644
--- a/internal/diagnostics/server.go
+++ b/internal/diagnostics/server.go
@@ -14,7 +14,7 @@ import (
 	"github.com/kong/go-database-reconciler/pkg/file"
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/fallback"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 )
 
 const (
@@ -138,7 +138,7 @@ func (s *Server) receiveConfig(ctx context.Context) {
 				s.logger.Error(err, "Shutting down diagnostic config collection: context completed with error")
 				return
 			}
-			s.logger.V(util.InfoLevel).Info("Shutting down diagnostic config collection: context completed")
+			s.logger.V(logging.InfoLevel).Info("Shutting down diagnostic config collection: context completed")
 			return
 		}
 	}
diff --git a/internal/gatewayapi/references.go b/internal/gatewayapi/references.go
index c97112739..ae331f771 100644
--- a/internal/gatewayapi/references.go
+++ b/internal/gatewayapi/references.go
@@ -1,9 +1,13 @@
 package gatewayapi
 
 import (
+	"fmt"
 	"reflect"
 
+	"github.com/go-logr/logr"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 )
 
 // RefChecker is a wrapper type that facilitates checking whether a backendRef is allowed
@@ -11,20 +15,23 @@ import (
 type RefChecker[T BackendRefT] struct {
 	target     client.Object
 	backendRef T
+	log        logr.Logger
 }
 
 // NewRefCheckerForRoute returns a RefChecker for the provided route and backendRef.
-func NewRefCheckerForRoute[T BackendRefT](route client.Object, ref T) RefChecker[T] {
+func NewRefCheckerForRoute[T BackendRefT](log logr.Logger, route client.Object, ref T) RefChecker[T] {
 	return RefChecker[T]{
 		target:     route,
 		backendRef: ref,
+		log:        log.WithName("refchecker"),
 	}
 }
 
-func NewRefCheckerForKongPlugin[T BackendRefT](target client.Object, requester T) RefChecker[T] {
+func NewRefCheckerForKongPlugin[T BackendRefT](log logr.Logger, target client.Object, requester T) RefChecker[T] {
 	return RefChecker[T]{
 		target:     target,
 		backendRef: requester,
+		log:        log.WithName("refchecker"),
 	}
 }
 
@@ -55,7 +62,9 @@ func (rc RefChecker[T]) IsRefAllowedByGrant(
 			return true
 		}
 
+		rc.log.V(logging.TraceLevel).Info("checking reference for BackendRef")
 		return isRefAllowedByGrant(
+			rc.log,
 			(*string)(br.Namespace),
 			(string)(br.Name),
 			(string)(*br.Group),
@@ -68,7 +77,9 @@ func (rc RefChecker[T]) IsRefAllowedByGrant(
 			return true
 		}
 
+		rc.log.V(logging.TraceLevel).Info("checking reference to Secret")
 		return isRefAllowedByGrant(
+			rc.log,
 			(*string)(br.Namespace),
 			(string)(br.Name),
 			(string)(*br.Group),
@@ -77,26 +88,58 @@ func (rc RefChecker[T]) IsRefAllowedByGrant(
 		)
 
 	case PluginLabelReference:
+		rc.log.V(logging.TraceLevel).Info("checking reference to KongPlugin")
 		if br.Namespace == nil {
 			return true
 		}
 
 		return isRefAllowedByGrant(
+			rc.log,
 			(br.Namespace),
 			(br.Name),
 			"configuration.konghq.com", // TODO https://github.com/Kong/kubernetes-ingress-controller/issues/6000
 			"KongPlugin",               // TODO These magic strings should become unnecessary once we work with client.Object
 			allowedRefs,
 		)
-	}
 
+		// TODO this is somewhat like the desired end state of issue #6000, but isn't viable at the moment because we
+		// don't actually have the From object here, we have the reference that describes it. the assertion here always
+		// fails because we've already extracted a type string into "br" from the reference. were we constructing an
+		// object from the reference that'd work, but refactoring that without a bunch of cases to build the object
+		// isn't obvious.
+
+		// default:
+		//	if obj, ok := br.(client.Object); ok {
+		//		if obj.GetNamespace() == "" {
+		//			return true
+		//		}
+		//		rc.log.V(logging.TraceLevel).Info(fmt.Sprintf("checking reference from client.Object (actual type %t", br))
+
+		//		return isRefAllowedByGrant(
+		//			rc.log,
+		//			lo.ToPtr(obj.GetNamespace()),
+		//			obj.GetName(),
+		//			obj.GetObjectKind().GroupVersionKind().Group,
+		//			obj.GetObjectKind().GroupVersionKind().Kind,
+		//			allowedRefs,
+		//		)
+		//	} else {
+		//		rc.log.V(logging.TraceLevel).Info(fmt.Sprintf("could not check reference for non-client.Object (actual type %t)", br))
+		//	}
+
+	}
 	return false
 }
 
+// TODO https://github.com/Kong/kubernetes-ingress-controller/issues/6000
+// this does not indicate the relationship between the NN+GK args and the allowed arg, which makes it rather
+// difficult to understand
+
 // isRefAllowedByGrant checks if backendRef is permitted by the provided namespace-indexed ReferenceGrantTo set: allowed.
 // allowed is assumed to contain Tos that only match the backendRef's parent's From, as returned by
 // GetPermittedForReferenceGrantFrom.
 func isRefAllowedByGrant(
+	log logr.Logger,
 	namespace *string,
 	name string,
 	group string,
@@ -107,19 +150,40 @@ func isRefAllowedByGrant(
 		// local references are always fine
 		return true
 	}
-	for _, to := range allowed[Namespace(*namespace)] {
+	scoped := log.WithValues(
+		"namespace", *namespace,
+		"requested-group", group,
+		"requested-kind", kind,
+		"requested-name", name,
+	)
+	scoped.V(logging.TraceLevel).Info(fmt.Sprintf("checking %d entries for namespace", len(allowed[Namespace(*namespace)])))
+	for i, to := range allowed[Namespace(*namespace)] {
+		toName := ""
+		if to.Name != nil {
+			toName = string(*to.Name)
+		}
+		scoped = scoped.WithValues(
+			"to-group", to.Group,
+			"to-kind", to.Kind,
+			"to-name", toName,
+			"to-index", i,
+		)
 		if string(to.Group) == group && string(to.Kind) == kind {
 			if to.Name != nil {
 				if string(*to.Name) == name {
+					scoped.V(logging.TraceLevel).Info("requested ref allowed by grant")
 					return true
 				}
 			} else {
 				// if no referent name specified, matching group/kind is sufficient
+				scoped.V(logging.TraceLevel).Info("requested ref allowed by grant To")
 				return true
 			}
 		}
+		scoped.V(logging.TraceLevel).Info("grant To did not match requested ref target")
 	}
 
+	scoped.V(logging.TraceLevel).Info("no grants matching requested ref target")
 	return false
 }
 
@@ -127,6 +191,7 @@ func isRefAllowedByGrant(
 // from a namespace to a slice of ReferenceGrant Tos. When a To is included in the slice, the key namespace has a
 // ReferenceGrant with those Tos and the input From.
 func GetPermittedForReferenceGrantFrom(
+	log logr.Logger,
 	from ReferenceGrantFrom,
 	grants []*ReferenceGrant,
 ) map[Namespace][]ReferenceGrantTo {
@@ -135,10 +200,44 @@ func GetPermittedForReferenceGrantFrom(
 	// grant namespace. this technically could add duplicate copies of the Tos if there are duplicate Froms (it makes
 	// no sense to add them, but it's allowed), but duplicate Tos are harmless (we only care about having at least one
 	// matching To when checking if a ReferenceGrant allows a reference)
+	scoped := log.WithName("refchecker")
 	for _, grant := range grants {
 		for _, otherFrom := range grant.Spec.From {
 			if reflect.DeepEqual(from, otherFrom) {
+				scoped.V(logging.TraceLevel).Info("grant from equal, adding to allowed",
+					"grant-namespace", grant.Namespace,
+					"grant-name", grant.Name,
+					"grant-from-namespace", otherFrom.Namespace,
+					"grant-from-group", otherFrom.Group,
+					"grant-from-kind", otherFrom.Kind,
+					"requested-from-namespace", from.Namespace,
+					"requested-from-group", from.Group,
+					"requested-from-kind", from.Kind,
+				)
 				allowed[Namespace(grant.ObjectMeta.Namespace)] = append(allowed[Namespace(grant.ObjectMeta.Namespace)], grant.Spec.To...)
+				for _, to := range grant.Spec.To {
+					name := ""
+					if to.Name != nil {
+						name = string(*to.Name)
+					}
+					scoped.V(logging.TraceLevel).Info("added ReferenceGrantTo to namespace allowed list",
+						"namespace", grant.ObjectMeta.Namespace,
+						"to-group", to.Group,
+						"to-kind", to.Kind,
+						"to-name", name,
+					)
+				}
+			} else {
+				scoped.V(logging.TraceLevel).Info("grant from not equal, excluding from allowed",
+					"grant-namespace", grant.Namespace,
+					"grant-name", grant.Name,
+					"grant-from-namespace", otherFrom.Namespace,
+					"grant-from-group", otherFrom.Group,
+					"grant-from-kind", otherFrom.Kind,
+					"requested-from-namespace", from.Namespace,
+					"requested-from-group", from.Group,
+					"requested-from-kind", from.Kind,
+				)
 			}
 		}
 	}
diff --git a/internal/konnect/node_agent.go b/internal/konnect/node_agent.go
index 2e3060120..8ac8a717a 100644
--- a/internal/konnect/node_agent.go
+++ b/internal/konnect/node_agent.go
@@ -13,7 +13,7 @@ import (
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/clients"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/konnect/nodes"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util/clock"
 )
 
@@ -162,7 +162,7 @@ func (a *NodeAgent) updateNodeLoop(ctx context.Context) {
 			a.logger.Info("Update node loop stopped", "message", err.Error())
 			return
 		case <-a.refreshTicker.Channel():
-			a.logger.V(util.DebugLevel).Info("Updating nodes on tick")
+			a.logger.V(logging.DebugLevel).Info("Updating nodes on tick")
 			err := a.updateNodes(ctx)
 			if err != nil {
 				a.logger.Error(err, "Failed to update nodes")
@@ -191,11 +191,11 @@ func (a *NodeAgent) subscribeConfigStatus(ctx context.Context) {
 			return
 		case configStatus := <-ch:
 			if configStatus == a.configStatus.Load() {
-				a.logger.V(util.DebugLevel).Info("Config status not changed, skipping update")
+				a.logger.V(logging.DebugLevel).Info("Config status not changed, skipping update")
 				continue
 			}
 
-			a.logger.V(util.DebugLevel).Info("Config status changed, updating nodes")
+			a.logger.V(logging.DebugLevel).Info("Config status changed, updating nodes")
 			a.configStatus.Store(configStatus)
 			if err := a.updateNodes(ctx); err != nil {
 				a.logger.Error(err, "Failed to update nodes after config status changed")
@@ -217,7 +217,7 @@ func (a *NodeAgent) subscribeToGatewayClientsChanges(ctx context.Context) {
 			a.logger.Info("Subscribe gateway clients changes loop stopped", "message", ctx.Err().Error())
 			return
 		case <-gatewayClientsChangedCh:
-			a.logger.V(util.DebugLevel).Info("Gateway clients changed, updating nodes")
+			a.logger.V(logging.DebugLevel).Info("Gateway clients changed, updating nodes")
 			if err := a.updateNodes(ctx); err != nil {
 				a.logger.Error(err, "Failed to update nodes after gateway clients changed")
 			}
@@ -238,7 +238,7 @@ func (a *NodeAgent) updateKICNode(ctx context.Context, existingNodes []*nodes.No
 			nodesWithSameName = append(nodesWithSameName, node)
 		} else {
 			// delete the nodes with different name of the current node, since only on KIC node is allowed in the control plane.
-			a.logger.V(util.DebugLevel).Info("Remove outdated KIC node", "node_id", node.ID, "hostname", node.Hostname)
+			a.logger.V(logging.DebugLevel).Info("Remove outdated KIC node", "node_id", node.ID, "hostname", node.Hostname)
 			err := a.nodeClient.DeleteNode(ctx, node.ID)
 			if err != nil {
 				a.logger.Error(err, "Failed to delete KIC node", "node_id", node.ID, "hostname", node.Hostname)
@@ -271,7 +271,7 @@ func (a *NodeAgent) updateKICNode(ctx context.Context, existingNodes []*nodes.No
 
 	// create a new node if there is no existing node with same name as the current KIC node.
 	if len(nodesWithSameName) == 0 {
-		a.logger.V(util.DebugLevel).Info("No nodes found for KIC pod, should create one", "hostname", a.hostname)
+		a.logger.V(logging.DebugLevel).Info("No nodes found for KIC pod, should create one", "hostname", a.hostname)
 		createNodeReq := &nodes.CreateNodeRequest{
 			ID:       a.managerInstanceIDProvider.GetID().String(),
 			Hostname: a.hostname,
@@ -302,7 +302,7 @@ func (a *NodeAgent) updateKICNode(ctx context.Context, existingNodes []*nodes.No
 		a.logger.Error(err, "Failed to update node for KIC")
 		return err
 	}
-	a.logger.V(util.DebugLevel).Info("Updated last ping time of node for KIC", "node_id", latestNode.ID, "hostname", a.hostname)
+	a.logger.V(logging.DebugLevel).Info("Updated last ping time of node for KIC", "node_id", latestNode.ID, "hostname", a.hostname)
 
 	// treat more nodes with the same name as outdated, and remove them.
 	for i := 1; i < len(nodesWithSameName); i++ {
@@ -312,7 +312,7 @@ func (a *NodeAgent) updateKICNode(ctx context.Context, existingNodes []*nodes.No
 			a.logger.Error(err, "Failed to delete outdated KIC node", "node_id", node.ID, "hostname", node.Hostname)
 			continue
 		}
-		a.logger.V(util.DebugLevel).Info("Removed outdated KIC node", "node_id", node.ID, "hostname", node.Hostname)
+		a.logger.V(logging.DebugLevel).Info("Removed outdated KIC node", "node_id", node.ID, "hostname", node.Hostname)
 	}
 	return nil
 }
@@ -371,7 +371,7 @@ func (a *NodeAgent) updateGatewayNodes(ctx context.Context, existingNodes []*nod
 			a.logger.Error(err, "Failed to update kong gateway node", "hostname", gateway.Hostname, "node_id", latestNode.ID)
 			continue
 		}
-		a.logger.V(util.DebugLevel).Info("Updated kong gateway node", "hostname", gateway.Hostname, "node_id", latestNode.ID)
+		a.logger.V(logging.DebugLevel).Info("Updated kong gateway node", "hostname", gateway.Hostname, "node_id", latestNode.ID)
 		// succeeded to update node, remove the other outdated nodes.
 		for i := 1; i < len(ns); i++ {
 			node := ns[i]
@@ -380,7 +380,7 @@ func (a *NodeAgent) updateGatewayNodes(ctx context.Context, existingNodes []*nod
 				a.logger.Error(err, "Failed to delete outdated kong gateway node", "node_id", node.ID, "hostname", node.Hostname)
 				continue
 			}
-			a.logger.V(util.DebugLevel).Info("Removed outdated kong gateway node", "node_id", node.ID, "hostname", node.Hostname)
+			a.logger.V(logging.DebugLevel).Info("Removed outdated kong gateway node", "node_id", node.ID, "hostname", node.Hostname)
 		}
 
 	}
@@ -394,7 +394,7 @@ func (a *NodeAgent) updateGatewayNodes(ctx context.Context, existingNodes []*nod
 					a.logger.Error(err, "Failed to delete outdated kong gateway node", "node_id", node.ID, "hostname", node.Hostname)
 					continue
 				}
-				a.logger.V(util.DebugLevel).Info("Removed outdated kong gateway node", "node_id", node.ID, "hostname", node.Hostname)
+				a.logger.V(logging.DebugLevel).Info("Removed outdated kong gateway node", "node_id", node.ID, "hostname", node.Hostname)
 			}
 		}
 	}
diff --git a/internal/license/agent.go b/internal/license/agent.go
index 6a46e026c..1d1cc84f1 100644
--- a/internal/license/agent.go
+++ b/internal/license/agent.go
@@ -10,7 +10,7 @@ import (
 	"github.com/samber/lo"
 	"github.com/samber/mo"
 
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util/clock"
 )
 
@@ -119,7 +119,7 @@ func (a *Agent) NeedLeaderElection() bool {
 // Start starts the Agent. It attempts to pull an initial license from upstream, and then polls for updates on a
 // regular period, either the agent's initialPollingPeriod if it has not yet obtained a license or regularPollingPeriod if it has.
 func (a *Agent) Start(ctx context.Context) error {
-	a.logger.V(util.DebugLevel).Info("Starting license agent")
+	a.logger.V(logging.DebugLevel).Info("Starting license agent")
 
 	err := a.reconcileLicenseWithKonnect(ctx)
 	if err != nil {
@@ -134,7 +134,7 @@ func (a *Agent) Start(ctx context.Context) error {
 // as Kong will auto-populate these when adding the license to its config database.
 // It's optional because we may not have retrieved a license yet.
 func (a *Agent) GetLicense() mo.Option[kong.License] {
-	a.logger.V(util.DebugLevel).Info("Retrieving license from cache")
+	a.logger.V(logging.DebugLevel).Info("Retrieving license from cache")
 	a.mutex.RLock()
 	defer a.mutex.RUnlock()
 
@@ -164,7 +164,7 @@ func (a *Agent) runPollingLoop(ctx context.Context) error {
 	for {
 		select {
 		case <-ch:
-			a.logger.V(util.DebugLevel).Info("Retrieving license from external service")
+			a.logger.V(logging.DebugLevel).Info("Retrieving license from external service")
 			if err := a.reconcileLicenseWithKonnect(ctx); err != nil {
 				a.logger.Error(err, "Could not reconcile license with Konnect")
 			}
@@ -197,23 +197,23 @@ func (a *Agent) reconcileLicenseWithKonnect(ctx context.Context) error {
 	retrievedLicense, retrievedLicenseOk := retrievedLicenseOpt.Get()
 	if !retrievedLicenseOk {
 		// If we get no license from Konnect, we cannot do anything.
-		a.logger.V(util.DebugLevel).Info("No license found in Konnect")
+		a.logger.V(logging.DebugLevel).Info("No license found in Konnect")
 		return nil
 	}
 
 	if a.cachedLicense.IsAbsent() {
-		a.logger.V(util.InfoLevel).Info("Caching initial license retrieved from the upstream",
+		a.logger.V(logging.InfoLevel).Info("Caching initial license retrieved from the upstream",
 			"updated_at", retrievedLicense.UpdatedAt.String(),
 		)
 		a.updateCache(retrievedLicense)
 	} else if cachedLicense, ok := a.cachedLicense.Get(); ok && retrievedLicense.UpdatedAt.After(cachedLicense.UpdatedAt) {
-		a.logger.V(util.InfoLevel).Info("Caching license retrieved from the upstream as it is newer than the cached one",
+		a.logger.V(logging.InfoLevel).Info("Caching license retrieved from the upstream as it is newer than the cached one",
 			"cached_updated_at", cachedLicense.UpdatedAt.String(),
 			"retrieved_updated_at", retrievedLicense.UpdatedAt.String(),
 		)
 		a.updateCache(retrievedLicense)
 	} else {
-		a.logger.V(util.DebugLevel).Info("License cache is up to date")
+		a.logger.V(logging.DebugLevel).Info("License cache is up to date")
 	}
 
 	return nil
diff --git a/internal/util/logging.go b/internal/logging/logging.go
similarity index 99%
rename from internal/util/logging.go
rename to internal/logging/logging.go
index d0b90d5e9..0fba1da6d 100644
--- a/internal/util/logging.go
+++ b/internal/logging/logging.go
@@ -1,4 +1,4 @@
-package util
+package logging
 
 import (
 	"fmt"
diff --git a/internal/manager/setup.go b/internal/manager/setup.go
index ec61cbae0..4322d1ae7 100644
--- a/internal/manager/setup.go
+++ b/internal/manager/setup.go
@@ -33,9 +33,9 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/translator"
 	konnectLicense "github.com/kong/kubernetes-ingress-controller/v3/internal/konnect/license"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/license"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/manager/scheme"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object/status"
 )
 
@@ -45,7 +45,7 @@ import (
 
 // SetupLoggers sets up the loggers for the controller manager.
 func SetupLoggers(c *Config, output io.Writer) (logr.Logger, error) {
-	zapBase, err := util.MakeLogger(c.LogLevel, c.LogFormat, output)
+	zapBase, err := logging.MakeLogger(c.LogLevel, c.LogFormat, output)
 	if err != nil {
 		return logr.Logger{}, fmt.Errorf("failed to make logger: %w", err)
 	}
diff --git a/internal/store/store.go b/internal/store/store.go
index 32759f0f2..1e39ae73a 100644
--- a/internal/store/store.go
+++ b/internal/store/store.go
@@ -40,7 +40,7 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/annotations"
 	ctrlutils "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers/utils"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1"
 	kongv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1alpha1"
 	kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1beta1"
@@ -180,7 +180,7 @@ func (s Store) ListIngressesV1() []*netv1.Ingress {
 		default:
 			class, err := s.GetIngressClassV1(s.ingressClass)
 			if err != nil {
-				s.logger.V(util.DebugLevel).Info("IngressClass not found", "class", s.ingressClass)
+				s.logger.V(logging.DebugLevel).Info("IngressClass not found", "class", s.ingressClass)
 				continue
 			}
 			if !ctrlutils.IsDefaultIngressClass(class) {
@@ -704,7 +704,7 @@ func (s Store) ListKongCustomEntities() []*kongv1alpha1.KongCustomEntity {
 func (s Store) getIngressClassHandling() annotations.ClassMatching {
 	class, err := s.GetIngressClassV1(s.ingressClass)
 	if err != nil {
-		s.logger.V(util.DebugLevel).Info("IngressClass not found", "class", s.ingressClass)
+		s.logger.V(logging.DebugLevel).Info("IngressClass not found", "class", s.ingressClass)
 		return annotations.ExactClassMatch
 	}
 	if ctrlutils.IsDefaultIngressClass(class) {
diff --git a/internal/util/objectmeta.go b/internal/util/objectmeta.go
index 1792b2443..9a763242e 100644
--- a/internal/util/objectmeta.go
+++ b/internal/util/objectmeta.go
@@ -1,8 +1,7 @@
 package util
 
 import (
-	"maps"
-
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
@@ -15,14 +14,27 @@ type K8sObjectInfo struct {
 	GroupVersionKind schema.GroupVersionKind
 }
 
+// FromK8sObject extracts information from a Kubernetes object.
+// It performs a shallow copy of object annotations so any modifications after
+// calling FromK8sObject will have an effect on the original object.
 func FromK8sObject(obj client.Object) K8sObjectInfo {
-	ret := K8sObjectInfo{
-		Name:        obj.GetName(),
-		Namespace:   obj.GetNamespace(),
-		Annotations: maps.Clone(obj.GetAnnotations()),
+	return K8sObjectInfo{
+		Name:             obj.GetName(),
+		Namespace:        obj.GetNamespace(),
+		Annotations:      obj.GetAnnotations(),
+		GroupVersionKind: obj.GetObjectKind().GroupVersionKind(),
 	}
-	if gvk := obj.GetObjectKind().GroupVersionKind(); gvk.String() != "" {
-		ret.GroupVersionKind = gvk
+}
+
+// TypeMetaFromGVK returns typemeta from groupversionkind of a k8s object.
+func TypeMetaFromGVK(gvk schema.GroupVersionKind) metav1.TypeMeta {
+	return metav1.TypeMeta{
+		APIVersion: gvk.GroupVersion().String(),
+		Kind:       gvk.Kind,
 	}
-	return ret
+}
+
+// TypeMetaFromK8sObject returns the typemeta of a client Object by its GVK.
+func TypeMetaFromK8sObject(obj client.Object) metav1.TypeMeta {
+	return TypeMetaFromGVK(obj.GetObjectKind().GroupVersionKind())
 }


Test patch:
diff --git a/internal/admission/handler_test.go b/internal/admission/handler_test.go
index eaf266fb4..9c8b943bc 100644
--- a/internal/admission/handler_test.go
+++ b/internal/admission/handler_test.go
@@ -21,7 +21,7 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/annotations"
 	ctrlref "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers/reference"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/labels"
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 	kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1"
 	kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1beta1"
 )
@@ -414,7 +414,7 @@ func TestHandleSecret(t *testing.T) {
 				Operation: admissionv1.Update,
 			}
 
-			logger := testr.NewWithOptions(t, testr.Options{Verbosity: util.DebugLevel})
+			logger := testr.NewWithOptions(t, testr.Options{Verbosity: logging.DebugLevel})
 			referenceIndexer := ctrlref.NewCacheIndexers(logger)
 
 			handler := RequestHandler{
diff --git a/internal/dataplane/kongstate/kongstate_test.go b/internal/dataplane/kongstate/kongstate_test.go
index 6ab9c89c6..fd243daf2 100644
--- a/internal/dataplane/kongstate/kongstate_test.go
+++ b/internal/dataplane/kongstate/kongstate_test.go
@@ -13,7 +13,6 @@ import (
 	"github.com/go-logr/logr/testr"
 	"github.com/go-logr/zapr"
 	"github.com/kong/go-kong/kong"
-	"github.com/kong/go-kong/kong/custom"
 	"github.com/samber/lo"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -27,6 +26,7 @@ import (
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/annotations"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/failures"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/labels"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
@@ -1528,285 +1528,119 @@ func (s *fakeSchemaGetter) Get(_ context.Context, entityType string) (kong.Schem
 	return schema, nil
 }
 
-func TestKongState_FillCustomEntities(t *testing.T) {
-	customEntityTypeMeta := metav1.TypeMeta{
-		APIVersion: kongv1alpha1.GroupVersion.Group + "/" + kongv1alpha1.GroupVersion.Version,
-		Kind:       "KongCustomEntity",
-	}
-	kongService1 := kong.Service{
-		Name: kong.String("service1"),
-	}
-	getKongServiceID := func(s *kong.Service) string {
-		err := s.FillID("")
-		require.NoError(t, err)
-		return *s.ID
+func TestIsRemotePluginReferenceAllowed(t *testing.T) {
+	serviceTypeMeta := metav1.TypeMeta{
+		Kind: "Service",
 	}
 
 	testCases := []struct {
-		name                        string
-		initialState                *KongState
-		customEntities              []*kongv1alpha1.KongCustomEntity
-		plugins                     []*kongv1.KongPlugin
-		schemas                     map[string]kong.Schema
-		expectedCustomEntities      map[string][]custom.Object
-		expectedTranslationFailures map[k8stypes.NamespacedName]string
+		name            string
+		referrer        client.Object
+		pluginNamespace string
+		pluginName      string
+		referenceGrants []*gatewayapi.ReferenceGrant
+		shouldAllow     bool
 	}{
 		{
-			name:         "single custom entity",
-			initialState: &KongState{},
-			customEntities: []*kongv1alpha1.KongCustomEntity{
-				{
-					TypeMeta: customEntityTypeMeta,
-					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "default",
-						Name:      "session-foo",
-					},
-					Spec: kongv1alpha1.KongCustomEntitySpec{
-						EntityType:     "sessions",
-						ControllerName: annotations.DefaultIngressClass,
-						Fields: apiextensionsv1.JSON{
-							Raw: []byte(`{"name":"session1"}`),
-						},
-					},
-				},
-			},
-			schemas: map[string]kong.Schema{
-				"sessions": {
-					"fields": []interface{}{
-						map[string]interface{}{
-							"name": map[string]interface{}{
-								"type":     "string",
-								"required": true,
-							},
-						},
-					},
-				},
-			},
-			expectedCustomEntities: map[string][]custom.Object{
-				"sessions": {
-					{
-						"name": "session1",
-					},
+			name: "no reference grant",
+			referrer: &corev1.Service{
+				TypeMeta: serviceTypeMeta,
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "foo",
+					Name:      "service-foo",
 				},
 			},
+			pluginNamespace: "bar",
+			pluginName:      "plugin-bar",
+			shouldAllow:     false,
 		},
 		{
-			name:         "custom entity with unknown type",
-			initialState: &KongState{},
-			customEntities: []*kongv1alpha1.KongCustomEntity{
-				{
-					TypeMeta: customEntityTypeMeta,
-					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "default",
-						Name:      "session-foo",
-					},
-					Spec: kongv1alpha1.KongCustomEntitySpec{
-						EntityType:     "sessions",
-						ControllerName: annotations.DefaultIngressClass,
-						Fields: apiextensionsv1.JSON{
-							Raw: []byte(`{"name":"session1"}`),
-						},
-					},
+			name: "have reference grant",
+			referrer: &corev1.Service{
+				TypeMeta: serviceTypeMeta,
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "foo",
+					Name:      "service-foo",
 				},
 			},
-			expectedTranslationFailures: map[k8stypes.NamespacedName]string{
-				{
-					Namespace: "default",
-					Name:      "session-foo",
-				}: "failed to fetch entity schema for entity type sessions: schema not found",
-			},
-		},
-		{
-			name:         "multiple custom entities with same type",
-			initialState: &KongState{},
-			customEntities: []*kongv1alpha1.KongCustomEntity{
-				{
-					TypeMeta: customEntityTypeMeta,
-					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "default",
-						Name:      "session-foo",
-					},
-					Spec: kongv1alpha1.KongCustomEntitySpec{
-						EntityType:     "sessions",
-						ControllerName: annotations.DefaultIngressClass,
-						Fields: apiextensionsv1.JSON{
-							Raw: []byte(`{"name":"session-foo"}`),
-						},
-					},
-				},
+			pluginNamespace: "bar",
+			pluginName:      "plugin-bar",
+			referenceGrants: []*gatewayapi.ReferenceGrant{
 				{
-					TypeMeta: customEntityTypeMeta,
 					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "default",
-						Name:      "session-bar",
-					},
-					Spec: kongv1alpha1.KongCustomEntitySpec{
-						EntityType:     "sessions",
-						ControllerName: annotations.DefaultIngressClass,
-						Fields: apiextensionsv1.JSON{
-							Raw: []byte(`{"name":"session-bar"}`),
-						},
+						Namespace: "bar", // same namespace as plugin
+						Name:      "grant-1",
 					},
-				},
-				{
-					TypeMeta: customEntityTypeMeta,
-					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "default-1",
-						Name:      "session-foo",
-					},
-					Spec: kongv1alpha1.KongCustomEntitySpec{
-						EntityType:     "sessions",
-						ControllerName: annotations.DefaultIngressClass,
-						Fields: apiextensionsv1.JSON{
-							Raw: []byte(`{"name":"session-foo-1"}`),
+					Spec: gatewayapi.ReferenceGrantSpec{
+						From: []gatewayapi.ReferenceGrantFrom{
+							{
+								Kind:      gatewayapi.Kind("Service"),
+								Namespace: "foo",
+							},
 						},
-					},
-				},
-			},
-			schemas: map[string]kong.Schema{
-				"sessions": {
-					"fields": []interface{}{
-						map[string]interface{}{
-							"name": map[string]interface{}{
-								"type":     "string",
-								"required": true,
+						To: []gatewayapi.ReferenceGrantTo{
+							{
+								Group: gatewayapi.Group(kongv1.GroupVersion.Group),
+								Kind:  gatewayapi.Kind("KongPlugin"),
 							},
 						},
 					},
 				},
 			},
-			expectedCustomEntities: map[string][]custom.Object{
-				// Should be sorted by original KCE namespace/name.
-				"sessions": {
-					{
-						// from default/bar
-						"name": "session-bar",
-					},
-					{
-						// from default/foo
-						"name": "session-foo",
-					},
-					{
-						// from default-1/foo
-						"name": "session-foo-1",
-					},
-				},
-			},
+			shouldAllow: true,
 		},
 		{
-			name: "custom entities with reference to other entities (services)",
-			initialState: &KongState{
-				Services: []Service{
-					{
-						Service: kongService1,
-						K8sServices: map[string]*corev1.Service{
-							"default/service1": {
-								ObjectMeta: metav1.ObjectMeta{
-									Namespace: "default",
-									Name:      "service1",
-									Annotations: map[string]string{
-										annotations.AnnotationPrefix + annotations.PluginsKey: "degraphql-1",
-									},
-								},
-							},
-						},
-					}, // Service: service1
-				}, // Services
-			},
-			customEntities: []*kongv1alpha1.KongCustomEntity{
-				{
-					TypeMeta: customEntityTypeMeta,
-					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "default",
-						Name:      "degraphql-1",
-					},
-					Spec: kongv1alpha1.KongCustomEntitySpec{
-						EntityType:     "degraphql_routes",
-						ControllerName: annotations.DefaultIngressClass,
-						Fields: apiextensionsv1.JSON{
-							Raw: []byte(`{"uri":"/api/me"}`),
-						},
-						ParentRef: &kongv1alpha1.ObjectReference{
-							Group: kong.String(kongv1.GroupVersion.Group),
-							Kind:  kong.String("KongPlugin"),
-							Name:  "degraphql-1",
-						},
-					},
+			name: "reference grant created but in different namespace",
+			referrer: &corev1.Service{
+				TypeMeta: serviceTypeMeta,
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "foo",
+					Name:      "service-foo",
 				},
 			},
-			plugins: []*kongv1.KongPlugin{
+			pluginNamespace: "bar",
+			pluginName:      "plugin-bar",
+			referenceGrants: []*gatewayapi.ReferenceGrant{
 				{
 					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "default",
-						Name:      "degraphql-1",
+						Namespace: "foo", // Not same namespace as plugin
+						Name:      "grant-1",
 					},
-					PluginName: "degraphql",
-				},
-			},
-			schemas: map[string]kong.Schema{
-				"degraphql_routes": {
-					"fields": []interface{}{
-						map[string]interface{}{
-							"uri": map[string]interface{}{
-								"type":     "string",
-								"required": true,
+					Spec: gatewayapi.ReferenceGrantSpec{
+						From: []gatewayapi.ReferenceGrantFrom{
+							{
+								Kind:      gatewayapi.Kind("Service"),
+								Namespace: "foo",
 							},
 						},
-						map[string]interface{}{
-							"service": map[string]interface{}{
-								"type":      "foreign",
-								"reference": "services",
+						To: []gatewayapi.ReferenceGrantTo{
+							{
+								Group: gatewayapi.Group(kongv1.GroupVersion.Group),
+								Kind:  gatewayapi.Kind("KongPlugin"),
 							},
 						},
 					},
 				},
 			},
-			expectedCustomEntities: map[string][]custom.Object{
-				"degraphql_routes": {
-					{
-						"uri": "/api/me",
-						"service": map[string]interface{}{
-							// ID generated from Kong service "service1" in workspace "".
-							"id": getKongServiceID(&kongService1),
-						},
-					},
-				},
-			},
+			shouldAllow: false,
 		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
 			s, err := store.NewFakeStore(store.FakeObjects{
-				KongCustomEntities: tc.customEntities,
-				KongPlugins:        tc.plugins,
+				ReferenceGrants: tc.referenceGrants,
 			})
 			require.NoError(t, err)
-			failuresCollector := failures.NewResourceFailuresCollector(logr.Discard())
-
-			ks := tc.initialState
-			ks.FillCustomEntities(
-				logr.Discard(), s,
-				failuresCollector,
-				&fakeSchemaGetter{schemas: tc.schemas}, "",
-			)
-			for entityType, expectedObjectList := range tc.expectedCustomEntities {
-				require.NotNil(t, ks.CustomEntities[entityType])
-				objectList := lo.Map(ks.CustomEntities[entityType].Entities, func(e CustomEntity, _ int) custom.Object {
-					return e.Object
-				})
-				require.Equal(t, expectedObjectList, objectList)
-			}
-
-			translationFailures := failuresCollector.PopResourceFailures()
-			for nsName, message := range tc.expectedTranslationFailures {
-				hasError := lo.ContainsBy(translationFailures, func(f failures.ResourceFailure) bool {
-					fmt.Println(f.Message())
-					return f.Message() == message && lo.ContainsBy(f.CausingObjects(), func(o client.Object) bool {
-						return o.GetNamespace() == nsName.Namespace && o.GetName() == nsName.Name
-					})
-				})
-				require.Truef(t, hasError, "translation error for KongCustomEntity %s not found", nsName)
+			err = isRemotePluginReferenceAllowed(logr.Discard(), s, pluginReference{
+				Referrer:  tc.referrer,
+				Namespace: tc.pluginNamespace,
+				Name:      tc.pluginName,
+			})
+			if tc.shouldAllow {
+				require.NoError(t, err)
+			} else {
+				require.Error(t, err)
 			}
 		})
 	}
diff --git a/internal/dataplane/translator/translate_utils_test.go b/internal/dataplane/translator/translate_utils_test.go
index 9bfb62425..97c6d8cf3 100644
--- a/internal/dataplane/translator/translate_utils_test.go
+++ b/internal/dataplane/translator/translate_utils_test.go
@@ -108,154 +108,6 @@ func TestConvertGatewayMatchHeadersToKongRouteMatchHeaders(t *testing.T) {
 	}
 }
 
-func TestGetPermittedForReferenceGrantFrom(t *testing.T) {
-	grants := []*gatewayapi.ReferenceGrant{
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      uuid.NewString(),
-				Namespace: "fitrat",
-			},
-			Spec: gatewayapi.ReferenceGrantSpec{
-				From: []gatewayapi.ReferenceGrantFrom{
-					{
-						Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-						Kind:      gatewayapi.Kind("TCPRoute"),
-						Namespace: gatewayapi.Namespace("garbage"),
-					},
-					{
-						Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-						Kind:      gatewayapi.Kind("TCPRoute"),
-						Namespace: gatewayapi.Namespace("behbudiy"),
-					},
-					{
-						Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-						Kind:      gatewayapi.Kind("TCPRoute"),
-						Namespace: gatewayapi.Namespace("qodiriy"),
-					},
-				},
-				To: []gatewayapi.ReferenceGrantTo{
-					{
-						Group: gatewayapi.Group(""),
-						Kind:  gatewayapi.Kind("GrantOne"),
-					},
-				},
-			},
-		},
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      uuid.NewString(),
-				Namespace: "cholpon",
-			},
-			Spec: gatewayapi.ReferenceGrantSpec{
-				From: []gatewayapi.ReferenceGrantFrom{
-					{
-						Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-						Kind:      gatewayapi.Kind("UDPRoute"),
-						Namespace: gatewayapi.Namespace("behbudiy"),
-					},
-					{
-						Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-						Kind:      gatewayapi.Kind("TCPRoute"),
-						Namespace: gatewayapi.Namespace("qodiriy"),
-					},
-				},
-				To: []gatewayapi.ReferenceGrantTo{
-					{
-						Group: gatewayapi.Group(""),
-						Kind:  gatewayapi.Kind("GrantTwo"),
-					},
-				},
-			},
-		},
-	}
-	tests := []struct {
-		msg    string
-		from   gatewayapi.ReferenceGrantFrom
-		result map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo
-	}{
-		{
-			msg: "no matches whatsoever",
-			from: gatewayapi.ReferenceGrantFrom{
-				Group:     gatewayapi.Group("invalid.example"),
-				Kind:      gatewayapi.Kind("invalid"),
-				Namespace: gatewayapi.Namespace("invalid"),
-			},
-			result: map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo{},
-		},
-		{
-			msg: "non-matching namespace",
-			from: gatewayapi.ReferenceGrantFrom{
-				Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-				Kind:      gatewayapi.Kind("UDPRoute"),
-				Namespace: gatewayapi.Namespace("niyazi"),
-			},
-			result: map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo{},
-		},
-		{
-			msg: "non-matching kind",
-			from: gatewayapi.ReferenceGrantFrom{
-				Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-				Kind:      gatewayapi.Kind("TLSRoute"),
-				Namespace: gatewayapi.Namespace("behbudiy"),
-			},
-			result: map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo{},
-		},
-		{
-			msg: "non-matching group",
-			from: gatewayapi.ReferenceGrantFrom{
-				Group:     gatewayapi.Group("invalid.example"),
-				Kind:      gatewayapi.Kind("UDPRoute"),
-				Namespace: gatewayapi.Namespace("behbudiy"),
-			},
-			result: map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo{},
-		},
-		{
-			msg: "single match",
-			from: gatewayapi.ReferenceGrantFrom{
-				Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-				Kind:      gatewayapi.Kind("UDPRoute"),
-				Namespace: gatewayapi.Namespace("behbudiy"),
-			},
-			result: map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo{
-				"cholpon": {
-					{
-						Group: gatewayapi.Group(""),
-						Kind:  gatewayapi.Kind("GrantTwo"),
-					},
-				},
-			},
-		},
-		{
-			msg: "multiple matches",
-			from: gatewayapi.ReferenceGrantFrom{
-				Group:     gatewayapi.Group("gateway.networking.k8s.io"),
-				Kind:      gatewayapi.Kind("TCPRoute"),
-				Namespace: gatewayapi.Namespace("qodiriy"),
-			},
-			result: map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo{
-				"cholpon": {
-					{
-						Group: gatewayapi.Group(""),
-						Kind:  gatewayapi.Kind("GrantTwo"),
-					},
-				},
-				"fitrat": {
-					{
-						Group: gatewayapi.Group(""),
-						Kind:  gatewayapi.Kind("GrantOne"),
-					},
-				},
-			},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.msg, func(t *testing.T) {
-			result := GetPermittedForReferenceGrantFrom(tt.from, grants)
-			assert.Equal(t, tt.result, result)
-		})
-	}
-}
-
 func TestGenerateKongServiceFromBackendRef(t *testing.T) {
 	grants := []*gatewayapi.ReferenceGrant{
 		{
diff --git a/internal/dataplane/translator/wrappers_refchecker_test.go b/internal/dataplane/translator/wrappers_refchecker_test.go
index 9a18525c5..f92300066 100644
--- a/internal/dataplane/translator/wrappers_refchecker_test.go
+++ b/internal/dataplane/translator/wrappers_refchecker_test.go
@@ -3,6 +3,7 @@ package translator
 import (
 	"testing"
 
+	"github.com/go-logr/logr"
 	"github.com/samber/lo"
 	"github.com/stretchr/testify/require"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -111,7 +112,7 @@ func TestRefChecker_IsRefAllowedByGrant(t *testing.T) {
 
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
-			rc := gatewayapi.NewRefCheckerForRoute(tc.route, tc.backendRef)
+			rc := gatewayapi.NewRefCheckerForRoute(logr.Discard(), tc.route, tc.backendRef)
 			result := rc.IsRefAllowedByGrant(tc.allowedRefs)
 			require.Equal(t, tc.expected, result)
 		})
diff --git a/internal/gatewayapi/references_test.go b/internal/gatewayapi/references_test.go
new file mode 100644
index 000000000..1f9b32fb3
--- /dev/null
+++ b/internal/gatewayapi/references_test.go
@@ -0,0 +1,158 @@
+package gatewayapi
+
+import (
+	"testing"
+
+	"github.com/go-logr/logr"
+	"github.com/google/uuid"
+	"github.com/stretchr/testify/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestGetPermittedForReferenceGrantFrom(t *testing.T) {
+	grants := []*ReferenceGrant{
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      uuid.NewString(),
+				Namespace: "fitrat",
+			},
+			Spec: ReferenceGrantSpec{
+				From: []ReferenceGrantFrom{
+					{
+						Group:     Group("gateway.networking.k8s.io"),
+						Kind:      Kind("TCPRoute"),
+						Namespace: Namespace("garbage"),
+					},
+					{
+						Group:     Group("gateway.networking.k8s.io"),
+						Kind:      Kind("TCPRoute"),
+						Namespace: Namespace("behbudiy"),
+					},
+					{
+						Group:     Group("gateway.networking.k8s.io"),
+						Kind:      Kind("TCPRoute"),
+						Namespace: Namespace("qodiriy"),
+					},
+				},
+				To: []ReferenceGrantTo{
+					{
+						Group: Group(""),
+						Kind:  Kind("GrantOne"),
+					},
+				},
+			},
+		},
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      uuid.NewString(),
+				Namespace: "cholpon",
+			},
+			Spec: ReferenceGrantSpec{
+				From: []ReferenceGrantFrom{
+					{
+						Group:     Group("gateway.networking.k8s.io"),
+						Kind:      Kind("UDPRoute"),
+						Namespace: Namespace("behbudiy"),
+					},
+					{
+						Group:     Group("gateway.networking.k8s.io"),
+						Kind:      Kind("TCPRoute"),
+						Namespace: Namespace("qodiriy"),
+					},
+				},
+				To: []ReferenceGrantTo{
+					{
+						Group: Group(""),
+						Kind:  Kind("GrantTwo"),
+					},
+				},
+			},
+		},
+	}
+	tests := []struct {
+		msg    string
+		from   ReferenceGrantFrom
+		result map[Namespace][]ReferenceGrantTo
+	}{
+		{
+			msg: "no matches whatsoever",
+			from: ReferenceGrantFrom{
+				Group:     Group("invalid.example"),
+				Kind:      Kind("invalid"),
+				Namespace: Namespace("invalid"),
+			},
+			result: map[Namespace][]ReferenceGrantTo{},
+		},
+		{
+			msg: "non-matching namespace",
+			from: ReferenceGrantFrom{
+				Group:     Group("gateway.networking.k8s.io"),
+				Kind:      Kind("UDPRoute"),
+				Namespace: Namespace("niyazi"),
+			},
+			result: map[Namespace][]ReferenceGrantTo{},
+		},
+		{
+			msg: "non-matching kind",
+			from: ReferenceGrantFrom{
+				Group:     Group("gateway.networking.k8s.io"),
+				Kind:      Kind("TLSRoute"),
+				Namespace: Namespace("behbudiy"),
+			},
+			result: map[Namespace][]ReferenceGrantTo{},
+		},
+		{
+			msg: "non-matching group",
+			from: ReferenceGrantFrom{
+				Group:     Group("invalid.example"),
+				Kind:      Kind("UDPRoute"),
+				Namespace: Namespace("behbudiy"),
+			},
+			result: map[Namespace][]ReferenceGrantTo{},
+		},
+		{
+			msg: "single match",
+			from: ReferenceGrantFrom{
+				Group:     Group("gateway.networking.k8s.io"),
+				Kind:      Kind("UDPRoute"),
+				Namespace: Namespace("behbudiy"),
+			},
+			result: map[Namespace][]ReferenceGrantTo{
+				"cholpon": {
+					{
+						Group: Group(""),
+						Kind:  Kind("GrantTwo"),
+					},
+				},
+			},
+		},
+		{
+			msg: "multiple matches",
+			from: ReferenceGrantFrom{
+				Group:     Group("gateway.networking.k8s.io"),
+				Kind:      Kind("TCPRoute"),
+				Namespace: Namespace("qodiriy"),
+			},
+			result: map[Namespace][]ReferenceGrantTo{
+				"cholpon": {
+					{
+						Group: Group(""),
+						Kind:  Kind("GrantTwo"),
+					},
+				},
+				"fitrat": {
+					{
+						Group: Group(""),
+						Kind:  Kind("GrantOne"),
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.msg, func(t *testing.T) {
+			result := GetPermittedForReferenceGrantFrom(logr.Discard(), tt.from, grants)
+			assert.Equal(t, tt.result, result)
+		})
+	}
+}
diff --git a/internal/util/objectmeta_test.go b/internal/util/objectmeta_test.go
index 0419d85f6..04c49ac32 100644
--- a/internal/util/objectmeta_test.go
+++ b/internal/util/objectmeta_test.go
@@ -4,6 +4,8 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
 	netv1 "k8s.io/api/networking/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -46,7 +48,76 @@ func TestFromK8sObject(t *testing.T) {
 	} {
 		t.Run(tt.name, func(t *testing.T) {
 			got := FromK8sObject(tt.in)
-			assert.EqualValues(t, tt.want, got)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func BenchmarkFromK8sObject(b *testing.B) {
+	in := &netv1.Ingress{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:        "name",
+			Namespace:   "namespace",
+			Annotations: map[string]string{"a": "1", "b": "2"},
+		},
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Ingress",
+			APIVersion: "networking.k8s.io/v1",
+		},
+	}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		out := FromK8sObject(in)
+		_ = out
+	}
+}
+
+func TestTypeMetaFromK8sObject(t *testing.T) {
+	testCases := []struct {
+		name     string
+		obj      client.Object
+		typeMeta metav1.TypeMeta
+	}{
+		{
+			name: "empty group",
+			obj: &corev1.Service{
+				TypeMeta: metav1.TypeMeta{
+					APIVersion: "v1",
+					Kind:       "Service",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "svc",
+					Namespace: "default",
+				},
+			},
+			typeMeta: metav1.TypeMeta{
+				APIVersion: "v1",
+				Kind:       "Service",
+			},
+		},
+		{
+			name: "non-empty group",
+			obj: &netv1.Ingress{
+				TypeMeta: metav1.TypeMeta{
+					APIVersion: "networking.k8s.io/v1",
+					Kind:       "Ingress",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "ing",
+					Namespace: "default",
+				},
+			},
+			typeMeta: metav1.TypeMeta{
+				APIVersion: "networking.k8s.io/v1",
+				Kind:       "Ingress",
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			typeMeta := TypeMetaFromK8sObject(tc.obj)
+			require.Equal(t, tc.typeMeta, typeMeta)
 		})
 	}
 }
diff --git a/test/envtest/log.go b/test/envtest/log.go
index f35ca6c7b..30b0099ef 100644
--- a/test/envtest/log.go
+++ b/test/envtest/log.go
@@ -12,7 +12,7 @@ import (
 	"go.uber.org/zap/zaptest/observer"
 	ctrl "sigs.k8s.io/controller-runtime"
 
-	"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
+	"github.com/kong/kubernetes-ingress-controller/v3/internal/logging"
 )
 
 type LogsObserver interface {
@@ -41,7 +41,7 @@ func DumpLogsIfTestFailed(t *testing.T, logs LogsObserver) {
 		return
 	}
 
-	encoder, err := util.GetZapEncoding("text")
+	encoder, err := logging.GetZapEncoding("text")
 	require.NoError(t, err)
 
 	t.Logf("Test %s failed: dumping controller logs\n", t.Name())