@@ -531,6 +531,233 @@ public function testApplyToEvent(): void
531531 $ this ->assertSame ('566e3688a61d4bc888951642d6f14a19 ' , $ dynamicSamplingContext ->get ('trace_id ' ));
532532 }
533533
534+ public function testMergeScopesAppliesGlobalScopeUnderIsolationScope (): void
535+ {
536+ $ globalBreadcrumb = new Breadcrumb (Breadcrumb::LEVEL_INFO , Breadcrumb::TYPE_DEFAULT , 'global ' );
537+ $ isolationBreadcrumb = new Breadcrumb (Breadcrumb::LEVEL_INFO , Breadcrumb::TYPE_DEFAULT , 'isolation ' );
538+ $ globalAttachment = Attachment::fromBytes ('global.txt ' , 'global ' );
539+ $ isolationAttachment = Attachment::fromBytes ('isolation.txt ' , 'isolation ' );
540+
541+ $ globalUser = UserDataBag::createFromUserIdentifier ('global-user ' );
542+ $ globalUser ->setMetadata ('shared ' , 'global ' );
543+ $ globalUser ->setMetadata ('global ' , true );
544+
545+ $ globalScope = new Scope ();
546+ $ globalScope ->setTag ('shared ' , 'global ' );
547+ $ globalScope ->setTag ('global ' , 'tag ' );
548+ $ globalScope ->setExtra ('shared ' , 'global ' );
549+ $ globalScope ->setExtra ('global ' , true );
550+ $ globalScope ->setContext ('shared_context ' , ['value ' => 'global ' ]);
551+ $ globalScope ->setContext ('global_context ' , ['value ' => 'global ' ]);
552+ $ globalScope ->setUser ($ globalUser );
553+ $ globalScope ->setLevel (Severity::error ());
554+ $ globalScope ->setFingerprint (['global-fingerprint ' ]);
555+ $ globalScope ->addBreadcrumb ($ globalBreadcrumb );
556+ $ globalScope ->addFeatureFlag ('shared-flag ' , false );
557+ $ globalScope ->addFeatureFlag ('global-flag ' , true );
558+ $ globalScope ->addAttachment ($ globalAttachment );
559+
560+ $ isolationUser = UserDataBag::createFromUserIdentifier ('isolation-user ' );
561+ $ isolationUser ->setMetadata ('shared ' , 'isolation ' );
562+ $ isolationUser ->setMetadata ('isolation ' , true );
563+
564+ $ isolationScope = new Scope ();
565+ $ isolationScope ->setTag ('shared ' , 'isolation ' );
566+ $ isolationScope ->setTag ('isolation ' , 'tag ' );
567+ $ isolationScope ->setExtra ('shared ' , 'isolation ' );
568+ $ isolationScope ->setExtra ('isolation ' , true );
569+ $ isolationScope ->setContext ('shared_context ' , ['value ' => 'isolation ' ]);
570+ $ isolationScope ->setContext ('isolation_context ' , ['value ' => 'isolation ' ]);
571+ $ isolationScope ->setUser ($ isolationUser );
572+ $ isolationScope ->setLevel (Severity::warning ());
573+ $ isolationScope ->setFingerprint (['isolation-fingerprint ' ]);
574+ $ isolationScope ->addBreadcrumb ($ isolationBreadcrumb );
575+ $ isolationScope ->addFeatureFlag ('shared-flag ' , true );
576+ $ isolationScope ->addFeatureFlag ('isolation-flag ' , false );
577+ $ isolationScope ->addAttachment ($ isolationAttachment );
578+
579+ $ eventUser = UserDataBag::createFromUserIdentifier ('event-user ' );
580+ $ eventUser ->setMetadata ('shared ' , 'event ' );
581+ $ eventUser ->setMetadata ('event ' , true );
582+
583+ $ event = Event::createEvent ();
584+ $ event ->setTag ('shared ' , 'event ' );
585+ $ event ->setTag ('event ' , 'tag ' );
586+ $ event ->setExtra (['shared ' => 'event ' , 'event ' => true ]);
587+ $ event ->setContext ('shared_context ' , ['value ' => 'event ' ]);
588+ $ event ->setUser ($ eventUser );
589+ $ event ->setFingerprint (['event-fingerprint ' ]);
590+
591+ $ event = Scope::mergeScopes ($ globalScope , $ isolationScope )->applyToEvent ($ event );
592+
593+ $ this ->assertNotNull ($ event );
594+ $ this ->assertTrue ($ event ->getLevel ()->isEqualTo (Severity::warning ()));
595+ $ this ->assertSame (['event-fingerprint ' , 'global-fingerprint ' , 'isolation-fingerprint ' ], $ event ->getFingerprint ());
596+ $ this ->assertSame ([
597+ 'shared ' => 'event ' ,
598+ 'global ' => 'tag ' ,
599+ 'isolation ' => 'tag ' ,
600+ 'event ' => 'tag ' ,
601+ ], $ event ->getTags ());
602+ $ this ->assertSame ([
603+ 'shared ' => 'event ' ,
604+ 'global ' => true ,
605+ 'isolation ' => true ,
606+ 'event ' => true ,
607+ ], $ event ->getExtra ());
608+ $ this ->assertSame (['value ' => 'event ' ], $ event ->getContexts ()['shared_context ' ]);
609+ $ this ->assertSame (['value ' => 'global ' ], $ event ->getContexts ()['global_context ' ]);
610+ $ this ->assertSame (['value ' => 'isolation ' ], $ event ->getContexts ()['isolation_context ' ]);
611+ $ this ->assertSame ([
612+ 'values ' => [
613+ [
614+ 'flag ' => 'global-flag ' ,
615+ 'result ' => true ,
616+ ],
617+ [
618+ 'flag ' => 'shared-flag ' ,
619+ 'result ' => true ,
620+ ],
621+ [
622+ 'flag ' => 'isolation-flag ' ,
623+ 'result ' => false ,
624+ ],
625+ ],
626+ ], $ event ->getContexts ()['flags ' ]);
627+ $ this ->assertSame ([$ globalBreadcrumb , $ isolationBreadcrumb ], $ event ->getBreadcrumbs ());
628+ $ this ->assertSame ([$ globalAttachment , $ isolationAttachment ], $ event ->getAttachments ());
629+
630+ $ user = $ event ->getUser ();
631+ $ this ->assertNotNull ($ user );
632+ $ this ->assertSame ('event-user ' , $ user ->getId ());
633+ $ this ->assertSame ([
634+ 'shared ' => 'event ' ,
635+ 'global ' => true ,
636+ 'isolation ' => true ,
637+ 'event ' => true ,
638+ ], $ user ->getMetadata ());
639+ }
640+
641+ public function testMergeScopesUsesGlobalLevelWhenIsolationLevelIsUnset (): void
642+ {
643+ $ globalScope = new Scope ();
644+ $ globalScope ->setLevel (Severity::error ());
645+
646+ $ event = Scope::mergeScopes ($ globalScope , new Scope ())->applyToEvent (Event::createEvent ());
647+
648+ $ this ->assertNotNull ($ event );
649+ $ this ->assertTrue ($ event ->getLevel ()->isEqualTo (Severity::error ()));
650+ }
651+
652+ public function testMergeScopesCapsBreadcrumbsAndFlags (): void
653+ {
654+ $ globalScope = new Scope ();
655+ $ globalBreadcrumbs = [];
656+
657+ foreach (range (1 , 100 ) as $ i ) {
658+ $ breadcrumb = new Breadcrumb (Breadcrumb::LEVEL_INFO , Breadcrumb::TYPE_DEFAULT , "global {$ i }" );
659+ $ globalBreadcrumbs [] = $ breadcrumb ;
660+ $ globalScope ->addBreadcrumb ($ breadcrumb );
661+ $ globalScope ->addFeatureFlag ("feature {$ i }" , true );
662+ }
663+
664+ $ isolationBreadcrumb = new Breadcrumb (Breadcrumb::LEVEL_INFO , Breadcrumb::TYPE_DEFAULT , 'isolation ' );
665+ $ isolationScope = new Scope ();
666+ $ isolationScope ->addBreadcrumb ($ isolationBreadcrumb );
667+ $ isolationScope ->addFeatureFlag ('feature50 ' , false );
668+ $ isolationScope ->addFeatureFlag ('feature101 ' , true );
669+
670+ $ event = Scope::mergeScopes ($ globalScope , $ isolationScope )->applyToEvent (Event::createEvent ());
671+
672+ $ this ->assertNotNull ($ event );
673+ $ this ->assertCount (100 , $ event ->getBreadcrumbs ());
674+ $ this ->assertSame ($ globalBreadcrumbs [1 ], $ event ->getBreadcrumbs ()[0 ]);
675+ $ this ->assertSame ($ isolationBreadcrumb , $ event ->getBreadcrumbs ()[99 ]);
676+
677+ $ flags = $ event ->getContexts ()['flags ' ]['values ' ];
678+ $ this ->assertCount (Scope::MAX_FLAGS , $ flags );
679+ $ this ->assertSame ([
680+ 'flag ' => 'feature2 ' ,
681+ 'result ' => true ,
682+ ], $ flags [0 ]);
683+ $ this ->assertSame ([
684+ 'flag ' => 'feature50 ' ,
685+ 'result ' => false ,
686+ ], $ flags [98 ]);
687+ $ this ->assertSame ([
688+ 'flag ' => 'feature101 ' ,
689+ 'result ' => true ,
690+ ], $ flags [99 ]);
691+ $ this ->assertFalse (\in_array ('feature1 ' , array_column ($ flags , 'flag ' ), true ));
692+ }
693+
694+ public function testMergeScopesKeepsTraceStateFromIsolationScope (): void
695+ {
696+ $ globalPropagationContext = PropagationContext::fromDefaults ();
697+ $ globalPropagationContext ->setTraceId (new TraceId ('11111111111111111111111111111111 ' ));
698+ $ globalPropagationContext ->setSpanId (new SpanId ('1111111111111111 ' ));
699+
700+ $ globalSpan = new Span ();
701+ $ globalSpan ->setTraceId (new TraceId ('22222222222222222222222222222222 ' ));
702+ $ globalSpan ->setSpanId (new SpanId ('2222222222222222 ' ));
703+
704+ $ globalScope = new Scope ($ globalPropagationContext );
705+ $ globalScope ->setSpan ($ globalSpan );
706+
707+ $ isolationPropagationContext = PropagationContext::fromDefaults ();
708+ $ isolationPropagationContext ->setTraceId (new TraceId ('33333333333333333333333333333333 ' ));
709+ $ isolationPropagationContext ->setSpanId (new SpanId ('3333333333333333 ' ));
710+
711+ $ isolationScope = new Scope ($ isolationPropagationContext );
712+
713+ $ mergedScope = Scope::mergeScopes ($ globalScope , $ isolationScope );
714+
715+ $ this ->assertNull ($ mergedScope ->getSpan ());
716+ $ this ->assertNotSame ($ isolationScope ->getPropagationContext (), $ mergedScope ->getPropagationContext ());
717+ $ this ->assertSame ([
718+ 'trace_id ' => '33333333333333333333333333333333 ' ,
719+ 'span_id ' => '3333333333333333 ' ,
720+ ], $ mergedScope ->getTraceContext ());
721+
722+ $ event = $ mergedScope ->applyToEvent (Event::createEvent ());
723+
724+ $ this ->assertNotNull ($ event );
725+ $ this ->assertSame ([
726+ 'trace_id ' => '33333333333333333333333333333333 ' ,
727+ 'span_id ' => '3333333333333333 ' ,
728+ ], $ event ->getContexts ()['trace ' ]);
729+ }
730+
731+ public function testMergeScopesKeepsProcessorOrder (): void
732+ {
733+ $ calls = [];
734+
735+ Scope::addGlobalEventProcessor (static function (Event $ event ) use (&$ calls ): ?Event {
736+ $ calls [] = 'static ' ;
737+
738+ return $ event ;
739+ });
740+
741+ $ globalScope = new Scope ();
742+ $ globalScope ->addEventProcessor (static function (Event $ event ) use (&$ calls ): ?Event {
743+ $ calls [] = 'global ' ;
744+
745+ return $ event ;
746+ });
747+
748+ $ isolationScope = new Scope ();
749+ $ isolationScope ->addEventProcessor (static function (Event $ event ) use (&$ calls ): ?Event {
750+ $ calls [] = 'isolation ' ;
751+
752+ return $ event ;
753+ });
754+
755+ $ event = Scope::mergeScopes ($ globalScope , $ isolationScope )->applyToEvent (Event::createEvent ());
756+
757+ $ this ->assertNotNull ($ event );
758+ $ this ->assertSame (['static ' , 'global ' , 'isolation ' ], $ calls );
759+ }
760+
534761 /**
535762 * @dataProvider eventWithLogCountProvider
536763 */
0 commit comments