diff --git a/pkg/controllers/accesslogpolicy_controller.go b/pkg/controllers/accesslogpolicy_controller.go index c9ff5037..0888a002 100644 --- a/pkg/controllers/accesslogpolicy_controller.go +++ b/pkg/controllers/accesslogpolicy_controller.go @@ -104,7 +104,7 @@ func RegisterAccessLogPolicyController( } func (r *accessLogPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - ctx = gwlog.StartReconcileTrace(ctx, r.log, "accesslogpolicy", req.Name) + ctx = gwlog.StartReconcileTrace(ctx, r.log, "accesslogpolicy", req.Name, req.Namespace) defer func() { gwlog.EndReconcileTrace(ctx, r.log) }() diff --git a/pkg/controllers/gateway_controller.go b/pkg/controllers/gateway_controller.go index 93303f5e..6b406315 100644 --- a/pkg/controllers/gateway_controller.go +++ b/pkg/controllers/gateway_controller.go @@ -19,6 +19,7 @@ package controllers import ( "context" "fmt" + anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1" "github.com/aws/aws-application-networking-k8s/pkg/aws/services" "github.com/aws/aws-application-networking-k8s/pkg/controllers/eventhandlers" @@ -119,7 +120,7 @@ func RegisterGatewayController( //+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways/finalizers,verbs=update func (r *gatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - ctx = gwlog.StartReconcileTrace(ctx, r.log, "gateway", req.Name) + ctx = gwlog.StartReconcileTrace(ctx, r.log, "gateway", req.Name, req.Namespace) defer func() { gwlog.EndReconcileTrace(ctx, r.log) }() diff --git a/pkg/controllers/gatewayclass_controller.go b/pkg/controllers/gatewayclass_controller.go index 4939c0aa..d388d814 100644 --- a/pkg/controllers/gatewayclass_controller.go +++ b/pkg/controllers/gatewayclass_controller.go @@ -56,7 +56,7 @@ func RegisterGatewayClassController(log gwlog.Logger, mgr ctrl.Manager) error { //+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gatewayclasses/finalizers,verbs=update func (r *gatewayClassReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - ctx = gwlog.StartReconcileTrace(ctx, r.log, "gatewayclass", req.Name) + ctx = gwlog.StartReconcileTrace(ctx, r.log, "gatewayclass", req.Name, req.Namespace) defer func() { gwlog.EndReconcileTrace(ctx, r.log) }() diff --git a/pkg/controllers/iamauthpolicy_controller.go b/pkg/controllers/iamauthpolicy_controller.go index b84f5fc5..9d485ce0 100644 --- a/pkg/controllers/iamauthpolicy_controller.go +++ b/pkg/controllers/iamauthpolicy_controller.go @@ -78,7 +78,7 @@ func RegisterIAMAuthPolicyController(log gwlog.Logger, mgr ctrl.Manager, cloud p // // Policy Attachment Spec is defined in [GEP-713]: https://gateway-api.sigs.k8s.io/geps/gep-713/. func (c *IAMAuthPolicyController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - ctx = gwlog.StartReconcileTrace(ctx, c.log, "iamauthpolicy", req.Name) + ctx = gwlog.StartReconcileTrace(ctx, c.log, "iamauthpolicy", req.Name, req.Namespace) defer func() { gwlog.EndReconcileTrace(ctx, c.log) }() diff --git a/pkg/controllers/route_controller.go b/pkg/controllers/route_controller.go index ac598009..f252fd15 100644 --- a/pkg/controllers/route_controller.go +++ b/pkg/controllers/route_controller.go @@ -149,17 +149,11 @@ func RegisterAllRouteControllers( } func (r *routeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - ctx = gwlog.StartReconcileTrace(ctx, r.log, "route", req.Name) + ctx = gwlog.StartReconcileTrace(ctx, r.log, "route", req.Name, req.Namespace) defer func() { gwlog.EndReconcileTrace(ctx, r.log) }() - r.log.Infow(ctx, "reconcile starting", gwlog.GetMetadata(ctx)...) - - defer func() { - r.log.Infow(ctx, "reconcile completed", gwlog.GetMetadata(ctx)...) - }() - recErr := r.reconcile(ctx, req) if recErr != nil { r.log.Infow(ctx, "reconcile error", "name", req.Name, "message", recErr.Error()) diff --git a/pkg/controllers/service_controller.go b/pkg/controllers/service_controller.go index 28c15dfe..b1fc338e 100644 --- a/pkg/controllers/service_controller.go +++ b/pkg/controllers/service_controller.go @@ -73,7 +73,7 @@ func RegisterServiceController( //+kubebuilder:rbac:groups=core,resources=configmaps, verbs=create;delete;patch;update;get;list;watch func (r *serviceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - ctx = gwlog.StartReconcileTrace(ctx, r.log, "service", req.Name) + ctx = gwlog.StartReconcileTrace(ctx, r.log, "service", req.Name, req.Namespace) defer func() { gwlog.EndReconcileTrace(ctx, r.log) }() diff --git a/pkg/controllers/serviceexport_controller.go b/pkg/controllers/serviceexport_controller.go index e7bf1f74..e58db107 100644 --- a/pkg/controllers/serviceexport_controller.go +++ b/pkg/controllers/serviceexport_controller.go @@ -102,7 +102,7 @@ func RegisterServiceExportController( //+kubebuilder:rbac:groups=application-networking.k8s.aws,resources=serviceexports/finalizers,verbs=update func (r *serviceExportReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - ctx = gwlog.StartReconcileTrace(ctx, r.log, "serviceexport", req.Name) + ctx = gwlog.StartReconcileTrace(ctx, r.log, "serviceexport", req.Name, req.Namespace) defer func() { gwlog.EndReconcileTrace(ctx, r.log) }() diff --git a/pkg/controllers/serviceimport_controller.go b/pkg/controllers/serviceimport_controller.go index f3ae4717..45500ba5 100644 --- a/pkg/controllers/serviceimport_controller.go +++ b/pkg/controllers/serviceimport_controller.go @@ -71,7 +71,7 @@ func RegisterServiceImportController( //+kubebuilder:rbac:groups=application-networking.k8s.aws,resources=serviceimports/finalizers,verbs=update func (r *serviceImportReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - ctx = gwlog.StartReconcileTrace(ctx, r.log, "serviceimport", req.Name) + ctx = gwlog.StartReconcileTrace(ctx, r.log, "serviceimport", req.Name, req.Namespace) defer func() { gwlog.EndReconcileTrace(ctx, r.log) }() diff --git a/pkg/controllers/targetgrouppolicy_controller.go b/pkg/controllers/targetgrouppolicy_controller.go index d9ba79f2..cd6f202b 100644 --- a/pkg/controllers/targetgrouppolicy_controller.go +++ b/pkg/controllers/targetgrouppolicy_controller.go @@ -41,7 +41,7 @@ func RegisterTargetGroupPolicyController(log gwlog.Logger, mgr ctrl.Manager) err } func (c *TargetGroupPolicyController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - ctx = gwlog.StartReconcileTrace(ctx, c.log, "targetgrouppolicy", req.Name) + ctx = gwlog.StartReconcileTrace(ctx, c.log, "targetgrouppolicy", req.Name, req.Namespace) defer func() { gwlog.EndReconcileTrace(ctx, c.log) }() diff --git a/pkg/controllers/vpcassociationpolicy_controller.go b/pkg/controllers/vpcassociationpolicy_controller.go index 215df155..0b778621 100644 --- a/pkg/controllers/vpcassociationpolicy_controller.go +++ b/pkg/controllers/vpcassociationpolicy_controller.go @@ -55,7 +55,7 @@ func RegisterVpcAssociationPolicyController(log gwlog.Logger, cloud pkg_aws.Clou } func (c *vpcAssociationPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - ctx = gwlog.StartReconcileTrace(ctx, c.log, "vpcassociationpolicy", req.Name) + ctx = gwlog.StartReconcileTrace(ctx, c.log, "vpcassociationpolicy", req.Name, req.Namespace) defer func() { gwlog.EndReconcileTrace(ctx, c.log) }() diff --git a/pkg/deploy/lattice/listener_manager_test.go b/pkg/deploy/lattice/listener_manager_test.go index f16a1348..99b4e6d1 100644 --- a/pkg/deploy/lattice/listener_manager_test.go +++ b/pkg/deploy/lattice/listener_manager_test.go @@ -510,7 +510,7 @@ func Test_defaultListenerManager_getLatticeListenerDefaultAction_HTTP_HTTPS_List log: gwlog.FallbackLogger, cloud: cloud, } - got, err := d.getLatticeListenerDefaultAction(modelListener) + got, err := d.getLatticeListenerDefaultAction(context.TODO(), modelListener) if tt.wantErr { assert.Error(t, err) } else { @@ -643,7 +643,7 @@ func Test_ListenerManager_getLatticeListenerDefaultAction_TLS_PASSTHROUGH_Listen log: gwlog.FallbackLogger, cloud: cloud, } - gotDefaultAction, err := d.getLatticeListenerDefaultAction(modelListener) + gotDefaultAction, err := d.getLatticeListenerDefaultAction(context.TODO(), modelListener) if tt.wantErr { assert.Error(t, err) } else { diff --git a/pkg/deploy/lattice/target_group_manager.go b/pkg/deploy/lattice/target_group_manager.go index c17b56c5..268087aa 100644 --- a/pkg/deploy/lattice/target_group_manager.go +++ b/pkg/deploy/lattice/target_group_manager.go @@ -458,7 +458,7 @@ func (s *defaultTargetGroupManager) findSvcExportTG(ctx context.Context, svcImpo // ResolveRuleTgIds populates all target group ids in the rule's actions func (s *defaultTargetGroupManager) ResolveRuleTgIds(ctx context.Context, ruleAction *model.RuleAction, stack core.Stack) error { if len(ruleAction.TargetGroups) == 0 { - s.log.Debugf("no target groups to resolve for rule") + s.log.Debugf(ctx, "no target groups to resolve for rule") return nil } for i, ruleActionTg := range ruleAction.TargetGroups { @@ -466,16 +466,16 @@ func (s *defaultTargetGroupManager) ResolveRuleTgIds(ctx context.Context, ruleAc return errors.New("rule TG is missing a required target group identifier") } if ruleActionTg.LatticeTgId != "" { - s.log.Debugf("Rule TG %d already resolved %s", i, ruleActionTg.LatticeTgId) + s.log.Debugf(ctx, "Rule TG %d already resolved %s", i, ruleActionTg.LatticeTgId) continue } if ruleActionTg.StackTargetGroupId != "" { if ruleActionTg.StackTargetGroupId == model.InvalidBackendRefTgId { - s.log.Debugf("Rule TG has an invalid backendref, setting TG id to invalid") + s.log.Debugf(ctx, "Rule TG has an invalid backendref, setting TG id to invalid") ruleActionTg.LatticeTgId = model.InvalidBackendRefTgId continue } - s.log.Debugf("Fetching TG %d from the stack (ID %s)", i, ruleActionTg.StackTargetGroupId) + s.log.Debugf(ctx, "Fetching TG %d from the stack (ID %s)", i, ruleActionTg.StackTargetGroupId) stackTg := &model.TargetGroup{} err := stack.GetResource(ruleActionTg.StackTargetGroupId, stackTg) if err != nil { @@ -487,7 +487,7 @@ func (s *defaultTargetGroupManager) ResolveRuleTgIds(ctx context.Context, ruleAc ruleActionTg.LatticeTgId = stackTg.Status.Id } if ruleActionTg.SvcImportTG != nil { - s.log.Debugf("Getting target group for service import %s %s (%s, %s)", + s.log.Debugf(ctx, "Getting target group for service import %s %s (%s, %s)", ruleActionTg.SvcImportTG.K8SServiceName, ruleActionTg.SvcImportTG.K8SServiceNamespace, ruleActionTg.SvcImportTG.K8SClusterName, ruleActionTg.SvcImportTG.VpcId) tgId, err := s.findSvcExportTG(ctx, *ruleActionTg.SvcImportTG) diff --git a/pkg/gateway/model_build_lattice_service.go b/pkg/gateway/model_build_lattice_service.go index f3dd5cad..dd54994b 100644 --- a/pkg/gateway/model_build_lattice_service.go +++ b/pkg/gateway/model_build_lattice_service.go @@ -87,7 +87,7 @@ func (t *latticeServiceModelBuildTask) buildModel(ctx context.Context) error { t.log.Debugf(ctx, "Building rules for %d listeners", len(modelListeners)) for _, modelListener := range modelListeners { if modelListener.Spec.Protocol == vpclattice.ListenerProtocolTlsPassthrough { - t.log.Debugf("Skip building rules for TLS_PASSTHROUGH listener %s, since lattice TLS_PASSTHROUGH listener can only have listener defaultAction and without any other rule", modelListener.ID()) + t.log.Debugf(ctx, "Skip building rules for TLS_PASSTHROUGH listener %s, since lattice TLS_PASSTHROUGH listener can only have listener defaultAction and without any other rule", modelListener.ID()) continue } diff --git a/pkg/gateway/model_build_listener.go b/pkg/gateway/model_build_listener.go index 478cf6ba..ebd3471a 100644 --- a/pkg/gateway/model_build_listener.go +++ b/pkg/gateway/model_build_listener.go @@ -46,7 +46,7 @@ func (t *latticeServiceModelBuildTask) extractListenerInfo( listenerPort := int(section.Port) protocol := section.Protocol if isTLSPassthroughGatewayListener(§ion) { - t.log.Debugf("Found TLS passthrough section %v", section.TLS) + t.log.Debugf(ctx, "Found TLS passthrough section %v", section.TLS) protocol = vpclattice.ListenerProtocolTlsPassthrough } return int64(listenerPort), string(protocol), nil diff --git a/pkg/utils/gwlog/actions.go b/pkg/utils/gwlog/actions.go index ef5bb1dd..7de0edf7 100644 --- a/pkg/utils/gwlog/actions.go +++ b/pkg/utils/gwlog/actions.go @@ -1,4 +1,4 @@ package gwlog -const ReconcileStart = "[ACTION_RECONCILE_START]" -const ReconcileEnd = "[ACTION_RECONCILE_END]" +const ReconcileStart = "RECONCILE_START_MARKER" +const ReconcileEnd = "RECONCILE_END_MARKER" diff --git a/pkg/utils/gwlog/gwlog.go b/pkg/utils/gwlog/gwlog.go index 9e500793..2e56b966 100644 --- a/pkg/utils/gwlog/gwlog.go +++ b/pkg/utils/gwlog/gwlog.go @@ -14,97 +14,97 @@ type TracedLogger struct { InnerLogger *zap.SugaredLogger } -func (s *TracedLogger) Infoln(args ...interface{}) { - s.InnerLogger.Infoln(args...) +func (t *TracedLogger) Infoln(args ...interface{}) { + t.InnerLogger.Infoln(args...) } func (t *TracedLogger) Infow(ctx context.Context, msg string, keysAndValues ...interface{}) { - if GetTrace(ctx) != "" { - keysAndValues = append(keysAndValues, traceID, GetTrace(ctx)) + if tr := GetTraceID(ctx); tr != "" { + keysAndValues = append(keysAndValues, traceID, tr) } t.InnerLogger.Infow(msg, keysAndValues...) } func (t *TracedLogger) Infof(ctx context.Context, template string, args ...interface{}) { - if GetTrace(ctx) != "" { - t.InnerLogger.Infow(fmt.Sprintf(template, args...), traceID, GetTrace(ctx)) + if tr := GetTraceID(ctx); tr != "" { + t.InnerLogger.Infow(fmt.Sprintf(template, args...), traceID, tr) return } t.InnerLogger.Infof(template, args...) } func (t *TracedLogger) Info(ctx context.Context, msg string) { - if GetTrace(ctx) != "" { - t.InnerLogger.Infow(msg, traceID, GetTrace(ctx)) + if tr := GetTraceID(ctx); tr != "" { + t.InnerLogger.Infow(msg, traceID, tr) return } t.InnerLogger.Info(msg) } func (t *TracedLogger) Errorw(ctx context.Context, msg string, keysAndValues ...interface{}) { - if GetTrace(ctx) != "" { - keysAndValues = append(keysAndValues, traceID, GetTrace(ctx)) + if tr := GetTraceID(ctx); tr != "" { + keysAndValues = append(keysAndValues, traceID, tr) } t.InnerLogger.Errorw(msg, keysAndValues) } func (t *TracedLogger) Errorf(ctx context.Context, template string, args ...interface{}) { - if GetTrace(ctx) != "" { - t.InnerLogger.Errorw(fmt.Sprintf(template, args...), traceID, GetTrace(ctx)) + if tr := GetTraceID(ctx); tr != "" { + t.InnerLogger.Errorw(fmt.Sprintf(template, args...), traceID, tr) return } t.InnerLogger.Errorf(template, args...) } func (t *TracedLogger) Error(ctx context.Context, msg string) { - if GetTrace(ctx) != "" { - t.InnerLogger.Errorw(msg, traceID, GetTrace(ctx)) + if tr := GetTraceID(ctx); tr != "" { + t.InnerLogger.Errorw(msg, traceID, tr) return } t.InnerLogger.Error(msg) } func (t *TracedLogger) Debugw(ctx context.Context, msg string, keysAndValues ...interface{}) { - if GetTrace(ctx) != "" { - keysAndValues = append(keysAndValues, traceID, GetTrace(ctx)) + if tr := GetTraceID(ctx); tr != "" { + keysAndValues = append(keysAndValues, traceID, tr) } t.InnerLogger.Debugw(msg, keysAndValues...) } func (t *TracedLogger) Debugf(ctx context.Context, template string, args ...interface{}) { - if GetTrace(ctx) != "" { - t.InnerLogger.Debugw(fmt.Sprintf(template, args...), traceID, GetTrace(ctx)) + if tr := GetTraceID(ctx); tr != "" { + t.InnerLogger.Debugw(fmt.Sprintf(template, args...), traceID, tr) return } t.InnerLogger.Debugf(template, args...) } func (t *TracedLogger) Debug(ctx context.Context, msg string) { - if GetTrace(ctx) != "" { - t.InnerLogger.Debugw(msg, traceID, GetTrace(ctx)) + if tr := GetTraceID(ctx); tr != "" { + t.InnerLogger.Debugw(msg, traceID, tr) return } t.InnerLogger.Debug(msg) } func (t *TracedLogger) Warnw(ctx context.Context, msg string, keysAndValues ...interface{}) { - if GetTrace(ctx) != "" { - keysAndValues = append(keysAndValues, traceID, GetTrace(ctx)) + if tr := GetTraceID(ctx); tr != "" { + keysAndValues = append(keysAndValues, traceID, tr) } t.InnerLogger.Warnw(msg, keysAndValues...) } func (t *TracedLogger) Warnf(ctx context.Context, template string, args ...interface{}) { - if GetTrace(ctx) != "" { - t.InnerLogger.Warnw(fmt.Sprintf(template, args...), traceID, GetTrace(ctx)) + if tr := GetTraceID(ctx); tr != "" { + t.InnerLogger.Warnw(fmt.Sprintf(template, args...), traceID, tr) return } t.InnerLogger.Warnf(template, args...) } func (t *TracedLogger) Warn(ctx context.Context, msg string) { - if GetTrace(ctx) != "" { - t.InnerLogger.Warnw(msg, traceID, GetTrace(ctx)) + if tr := GetTraceID(ctx); tr != "" { + t.InnerLogger.Warnw(msg, traceID, tr) return } t.InnerLogger.Warn(msg) diff --git a/pkg/utils/gwlog/metadata.go b/pkg/utils/gwlog/metadata.go index 345cbb6d..959e3117 100644 --- a/pkg/utils/gwlog/metadata.go +++ b/pkg/utils/gwlog/metadata.go @@ -2,29 +2,27 @@ package gwlog import ( "context" + "github.com/google/uuid" ) -type key string - -const metadata key = "metadata" - +const metadataKey string = "metadata_key" const traceID string = "trace_id" -type metadataValue struct { +type metadata struct { m map[string]string } -func (mv *metadataValue) get(key string) string { +func (mv *metadata) get(key string) string { return mv.m[key] } -func (mv *metadataValue) set(key, val string) { +func (mv *metadata) set(key, val string) { mv.m[key] = val } -func newMetadata() *metadataValue { - return &metadataValue{ +func newMetadata() *metadata { + return &metadata{ m: make(map[string]string), } } @@ -32,23 +30,23 @@ func newMetadata() *metadataValue { func NewTrace(ctx context.Context) context.Context { currID := uuid.New() - newCtx := context.WithValue(ctx, metadata, newMetadata()) + newCtx := context.WithValue(ctx, metadataKey, newMetadata()) AddMetadata(newCtx, traceID, currID.String()) return newCtx } func AddMetadata(ctx context.Context, key, value string) { - if ctx.Value(metadata) != nil { - ctx.Value(metadata).(*metadataValue).set(key, value) + if ctx.Value(metadataKey) != nil { + ctx.Value(metadataKey).(*metadata).set(key, value) } } -func GetMetadata(ctx context.Context) []interface{} { +func getMetadata(ctx context.Context) []interface{} { var fields []interface{} - if ctx.Value(metadata) != nil { - for k, v := range ctx.Value(metadata).(*metadataValue).m { + if ctx.Value(metadataKey) != nil { + for k, v := range ctx.Value(metadataKey).(*metadata).m { if k == traceID { // skip since there's a separate method to grab the trace id continue @@ -60,27 +58,28 @@ func GetMetadata(ctx context.Context) []interface{} { return fields } -func GetTrace(ctx context.Context) string { - if ctx.Value(metadata) != nil { - m := ctx.Value(metadata).(*metadataValue).m +func GetTraceID(ctx context.Context) string { + if ctx.Value(metadataKey) != nil { + m := ctx.Value(metadataKey).(*metadata).m if m == nil { return "" } - return ctx.Value(metadata).(*metadataValue).m[traceID] + return ctx.Value(metadataKey).(*metadata).m[traceID] } return "" } -func StartReconcileTrace(ctx context.Context, log Logger, k8sresourcetype, name string) context.Context { +func StartReconcileTrace(ctx context.Context, log Logger, k8sresourcetype, name, namespace string) context.Context { ctx = NewTrace(ctx) AddMetadata(ctx, "type", k8sresourcetype) AddMetadata(ctx, "name", name) + AddMetadata(ctx, "namespace", namespace) - log.Infow(ctx, ReconcileStart, GetMetadata(ctx)...) + log.Infow(ctx, ReconcileStart, getMetadata(ctx)...) return ctx } func EndReconcileTrace(ctx context.Context, log Logger) { - log.Infow(ctx, ReconcileEnd, GetMetadata(ctx)...) + log.Infow(ctx, ReconcileEnd, getMetadata(ctx)...) } diff --git a/pkg/utils/gwlog/metadata_test.go b/pkg/utils/gwlog/metadata_test.go index 7d33ae49..91be7d3b 100644 --- a/pkg/utils/gwlog/metadata_test.go +++ b/pkg/utils/gwlog/metadata_test.go @@ -7,11 +7,11 @@ import ( ) func TestGetTrace(t *testing.T) { - if GetTrace(context.TODO()) != "" { + if GetTraceID(context.TODO()) != "" { t.Errorf("expected context with no trace_id to return empty string") } - if GetTrace(NewTrace(context.TODO())) == "" { + if GetTraceID(NewTrace(context.TODO())) == "" { t.Errorf("expected context with trace_id to return non-empty string") } } @@ -20,7 +20,7 @@ func TestMetadata(t *testing.T) { ctx := NewTrace(context.TODO()) AddMetadata(ctx, "foo", "bar") - md := GetMetadata(ctx) + md := getMetadata(ctx) mdMap := map[string]bool{} for _, m := range md { mdMap[fmt.Sprint(m)] = true