From 43ba279b5e8c5a09b4ec9681fef7a0e7797b19f2 Mon Sep 17 00:00:00 2001 From: Joseph Rodiz Date: Tue, 7 Apr 2026 10:38:00 -0600 Subject: [PATCH 1/4] Allow custom attributes on built-in traces via global attribute API Resolves firebase/firebase-android-sdk#6664. FirebasePerformance already had putAttribute/getAttribute/removeAttribute/ getAttributes backed by a ConcurrentHashMap, but they were @hide and not wired into built-in trace serialization. This change makes them public and ensures global attributes are included on all traces (built-in and custom). Changes: - Make global attribute methods (putAttribute, removeAttribute, getAttribute, getAttributes) public on FirebasePerformance - Expose MAX_TRACE_CUSTOM_ATTRIBUTES, MAX_ATTRIBUTE_KEY_LENGTH, MAX_ATTRIBUTE_VALUE_LENGTH as public constants on FirebasePerformance - Update api.txt with the expanded public API surface - Merge global attributes in TraceMetricBuilder (screen + custom Trace objects) - Add global attributes to AppStartTrace (_app_start) - Add global attributes to AppStateMonitor.sendSessionLog() (_fs, _bs) - Merge global attributes in NetworkRequestMetricBuilder (auto + manual HTTP) - Trace/request-level attributes take precedence over global on key conflicts Test coverage (TraceMetric.custom_attributes): - AppStartTraceTest: global attrs on _app_start - AppStateMonitorTest: global attrs on _app_in_foreground, _app_in_background, and screen traces (_st_*) - TraceMetricBuilderTest: global attrs merged, trace-level overrides global - NetworkRequestMetricBuilderTest: global attrs on network requests, per-request overrides global - TransportManagerTest: global attrs on built-in traces at ApplicationInfo level --- firebase-perf/CHANGELOG.md | 1 + firebase-perf/api.txt | 4 ++ .../firebase/perf/FirebasePerformance.java | 10 +-- .../perf/application/AppStateMonitor.java | 7 +++ .../firebase/perf/metrics/AppStartTrace.java | 7 +++ .../metrics/NetworkRequestMetricBuilder.java | 11 +++- .../perf/metrics/TraceMetricBuilder.java | 11 +++- .../perf/application/AppStateMonitorTest.java | 61 +++++++++++++++++++ .../perf/metrics/AppStartTraceTest.java | 29 +++++++++ .../NetworkRequestMetricBuilderTest.java | 29 +++++++++ .../perf/metrics/TraceMetricBuilderTest.java | 58 ++++++++++++++++++ .../perf/transport/TransportManagerTest.java | 42 +++++++++++++ 12 files changed, 261 insertions(+), 9 deletions(-) diff --git a/firebase-perf/CHANGELOG.md b/firebase-perf/CHANGELOG.md index 966a91944e3..03e8cf74f54 100644 --- a/firebase-perf/CHANGELOG.md +++ b/firebase-perf/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- [feature] Added support for custom attributes on built-in traces. Global attributes set via `FirebasePerformance.getInstance().putAttribute()` are now included in all built-in traces (`_app_start`, `_app_in_foreground`, `_app_in_background`, screen traces) and network requests. [#6664] - [changed] Bumped internal dependencies. # 22.0.4 diff --git a/firebase-perf/api.txt b/firebase-perf/api.txt index 13dfb7dcbea..d22c80e12c6 100644 --- a/firebase-perf/api.txt +++ b/firebase-perf/api.txt @@ -2,11 +2,15 @@ package com.google.firebase.perf { @javax.inject.Singleton public class FirebasePerformance { + method public String? getAttribute(String); + method public java.util.Map getAttributes(); method public static com.google.firebase.perf.FirebasePerformance getInstance(); method public boolean isPerformanceCollectionEnabled(); method public com.google.firebase.perf.metrics.HttpMetric newHttpMetric(String, @com.google.firebase.perf.FirebasePerformance.HttpMethod String); method public com.google.firebase.perf.metrics.HttpMetric newHttpMetric(java.net.URL, @com.google.firebase.perf.FirebasePerformance.HttpMethod String); method public com.google.firebase.perf.metrics.Trace newTrace(String); + method public void putAttribute(String, String); + method public void removeAttribute(String); method public void setPerformanceCollectionEnabled(boolean); method public static com.google.firebase.perf.metrics.Trace startTrace(String); field public static final int MAX_ATTRIBUTE_KEY_LENGTH = 40; // 0x28 diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerformance.java b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerformance.java index 40468566225..4dcbdb922ac 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerformance.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerformance.java @@ -71,15 +71,15 @@ public class FirebasePerformance implements FirebasePerformanceAttributable { /** Maximum allowed number of attributes allowed in a trace. */ @SuppressWarnings("unused") // Used in Javadoc. - private static final int MAX_TRACE_CUSTOM_ATTRIBUTES = Constants.MAX_TRACE_CUSTOM_ATTRIBUTES; + public static final int MAX_TRACE_CUSTOM_ATTRIBUTES = Constants.MAX_TRACE_CUSTOM_ATTRIBUTES; /** Maximum allowed length of the Key of the {@link Trace} attribute */ @SuppressWarnings("unused") // Used in Javadoc. - private static final int MAX_ATTRIBUTE_KEY_LENGTH = Constants.MAX_ATTRIBUTE_KEY_LENGTH; + public static final int MAX_ATTRIBUTE_KEY_LENGTH = Constants.MAX_ATTRIBUTE_KEY_LENGTH; /** Maximum allowed length of the Value of the {@link Trace} attribute */ @SuppressWarnings("unused") // Used in Javadoc. - private static final int MAX_ATTRIBUTE_VALUE_LENGTH = Constants.MAX_ATTRIBUTE_VALUE_LENGTH; + public static final int MAX_ATTRIBUTE_VALUE_LENGTH = Constants.MAX_ATTRIBUTE_VALUE_LENGTH; /** Maximum allowed length of the name of the {@link Trace} */ @SuppressWarnings("unused") // Used in Javadoc. @@ -332,7 +332,6 @@ public boolean isPerformanceCollectionEnabled() { * length is limited to {@link #MAX_ATTRIBUTE_KEY_LENGTH} * @param value value of the attribute. The max length is limited to {@link * #MAX_ATTRIBUTE_VALUE_LENGTH} - * @hide */ @Override public void putAttribute(@NonNull String attribute, @NonNull String value) { @@ -371,7 +370,6 @@ private void checkAttribute(@Nullable String key, @Nullable String value) { * Removes the attribute from the global list of attributes. * * @param attribute name of the attribute to be removed from the global pool. - * @hide */ @Override public void removeAttribute(@NonNull String attribute) { @@ -383,7 +381,6 @@ public void removeAttribute(@NonNull String attribute) { * * @param attribute name of the attribute to fetch the value for * @return the value of the attribute if it exists or null otherwise. - * @hide */ @Override @Nullable @@ -395,7 +392,6 @@ public String getAttribute(@NonNull String attribute) { * Returns the map of all the attributes currently added in the global pool. * * @return map of attributes and its values currently added to the running Traces - * @hide */ @Override @NonNull diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/application/AppStateMonitor.java b/firebase-perf/src/main/java/com/google/firebase/perf/application/AppStateMonitor.java index b014d82bb83..dd6e04ff084 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/application/AppStateMonitor.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/application/AppStateMonitor.java @@ -22,6 +22,7 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.FragmentActivity; +import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.logging.AndroidLogger; import com.google.firebase.perf.metrics.FrameMetricsCalculator.PerfFrameMetrics; @@ -391,6 +392,12 @@ private void sendSessionLog(String name, Timer startTime, Timer endTime) { // reset metrics. metricToCountMap.clear(); } + try { + metric.putAllCustomAttributes(FirebasePerformance.getInstance().getAttributes()); + } catch (IllegalStateException e) { + // FirebaseApp not initialized yet, skip global attributes + } + // The Foreground and Background trace marks the transition between the two states, // so we always specify the state to be ApplicationProcessState.FOREGROUND_BACKGROUND. transportManager.log(metric.build(), ApplicationProcessState.FOREGROUND_BACKGROUND); diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java index 813c8988383..212546c78c6 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java @@ -35,6 +35,7 @@ import androidx.lifecycle.ProcessLifecycleOwner; import com.google.firebase.FirebaseApp; import com.google.firebase.StartupTime; +import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.logging.AndroidLogger; import com.google.firebase.perf.session.PerfSession; @@ -478,6 +479,12 @@ private void logAppStartTrace() { metric.addAllSubtraces(subtraces).addPerfSessions(this.startSession.build()); + try { + metric.putAllCustomAttributes(FirebasePerformance.getInstance().getAttributes()); + } catch (IllegalStateException e) { + // FirebaseApp not initialized yet, skip global attributes + } + transportManager.log(metric.build(), ApplicationProcessState.FOREGROUND_BACKGROUND); } diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilder.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilder.java index 1e04744d1b2..9c442b45010 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilder.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilder.java @@ -18,6 +18,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.application.AppStateMonitor; import com.google.firebase.perf.application.AppStateUpdateHandler; import com.google.firebase.perf.logging.AndroidLogger; @@ -37,6 +38,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -213,7 +215,14 @@ public NetworkRequestMetricBuilder setRequestPayloadBytes(long bytes) { /** Sets the customAttributes for the current {@link NetworkRequestMetric}. */ public NetworkRequestMetricBuilder setCustomAttributes(Map attributes) { - builder.clearCustomAttributes().putAllCustomAttributes(attributes); + Map merged = new HashMap<>(); + try { + merged.putAll(FirebasePerformance.getInstance().getAttributes()); + } catch (IllegalStateException e) { + // FirebaseApp not initialized yet, skip global attributes + } + merged.putAll(attributes); + builder.clearCustomAttributes().putAllCustomAttributes(merged); return this; } diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/TraceMetricBuilder.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/TraceMetricBuilder.java index 424ef2c5d45..ceb75740829 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/TraceMetricBuilder.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/TraceMetricBuilder.java @@ -15,9 +15,11 @@ package com.google.firebase.perf.metrics; import androidx.annotation.NonNull; +import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.session.PerfSession; import com.google.firebase.perf.v1.TraceMetric; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -53,7 +55,14 @@ TraceMetric build() { } } - traceMetric.putAllCustomAttributes(trace.getAttributes()); + Map mergedAttributes = new HashMap<>(); + try { + mergedAttributes.putAll(FirebasePerformance.getInstance().getAttributes()); + } catch (IllegalStateException e) { + // FirebaseApp not initialized yet, skip global attributes + } + mergedAttributes.putAll(trace.getAttributes()); + traceMetric.putAllCustomAttributes(mergedAttributes); com.google.firebase.perf.v1.PerfSession[] perfSessions = PerfSession.buildAndSort(trace.getSessions()); diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java index 0b7d4bbfc17..5ed3d509dfb 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java @@ -33,6 +33,7 @@ import android.content.Context; import android.os.Bundle; import android.view.WindowManager.LayoutParams; +import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.FirebasePerformanceInitializer; import com.google.firebase.perf.FirebasePerformanceTestBase; import com.google.firebase.perf.config.ConfigResolver; @@ -771,6 +772,66 @@ public void appColdStart_singleSubscriberRegistersForMultipleTimes_oneCallbackIs verify(mockInitializer1, times(1)).onAppColdStart(); } + @Test + public void screenTrace_globalAttributesIncluded() { + FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); + firebasePerformance.putAttribute("globalKey", "globalValue"); + + AppStateMonitor monitor = new AppStateMonitor(transportManager, clock); + currentTime = 1; + monitor.onActivityStarted(activity1); + currentTime = 2; + monitor.onActivityStopped(activity1); + + verify(transportManager, times(1)) + .log(argTraceMetric.capture(), any(ApplicationProcessState.class)); + TraceMetric metric = argTraceMetric.getValue(); + assertThat(metric.getName()).startsWith(Constants.SCREEN_TRACE_PREFIX); + assertThat(metric.getCustomAttributesMap()).containsEntry("globalKey", "globalValue"); + + firebasePerformance.removeAttribute("globalKey"); + } + + @Test + public void foregroundTrace_globalAttributesIncluded() { + FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); + firebasePerformance.putAttribute("globalKey", "globalValue"); + + AppStateMonitor monitor = new AppStateMonitor(transportManager, clock); + currentTime = 1; + monitor.onActivityResumed(activity1); + currentTime = 2; + monitor.onActivityStopped(activity1); + + verify(transportManager, times(1)).log(argTraceMetric.capture(), eq(FOREGROUND_BACKGROUND)); + TraceMetric metric = argTraceMetric.getValue(); + Assert.assertEquals(Constants.TraceNames.FOREGROUND_TRACE_NAME.toString(), metric.getName()); + Assert.assertEquals("globalValue", metric.getCustomAttributesMap().get("globalKey")); + + firebasePerformance.removeAttribute("globalKey"); + } + + @Test + public void backgroundTrace_globalAttributesIncluded() { + FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); + firebasePerformance.putAttribute("globalKey", "globalValue"); + + AppStateMonitor monitor = new AppStateMonitor(transportManager, clock); + currentTime = 1; + monitor.onActivityResumed(activity1); + currentTime = 2; + monitor.onActivityStopped(activity1); + currentTime = 3; + monitor.onActivityResumed(activity1); + + verify(transportManager, times(2)).log(argTraceMetric.capture(), eq(FOREGROUND_BACKGROUND)); + TraceMetric metric = argTraceMetric.getValue(); + Assert.assertEquals(Constants.TraceNames.BACKGROUND_TRACE_NAME.toString(), metric.getName()); + Assert.assertEquals("globalValue", metric.getCustomAttributesMap().get("globalKey")); + + firebasePerformance.removeAttribute("globalKey"); + } + private static Activity createFakeActivity(boolean isHardwareAccelerated) { ActivityController fakeActivityController = Robolectric.buildActivity(Activity.class); diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java index daa80ad29a2..b34f9925bde 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java @@ -33,6 +33,7 @@ import android.os.SystemClock; import android.view.View; import androidx.test.core.app.ApplicationProvider; +import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.FirebasePerformanceTestBase; import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.session.PerfSession; @@ -341,4 +342,32 @@ public void timeToInitialDisplay_isLogged() { assertThat(ttid.getDurationUs()).isEqualTo(drawTime - appStartTime); assertThat(ttid.getSubtracesCount()).isEqualTo(3); } + + @Test + public void testAppStartTrace_globalAttributesIncluded() { + FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); + firebasePerformance.putAttribute("globalKey", "globalValue"); + + FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService(); + AppStartTrace trace = + new AppStartTrace(transportManager, clock, configResolver, fakeExecutorService); + trace.registerActivityLifecycleCallbacks(appContext); + currentTime = 1; + trace.onActivityCreated(activity1, bundle); + currentTime = 2; + trace.onActivityStarted(activity1); + currentTime = 3; + trace.onActivityResumed(activity1); + fakeExecutorService.runAll(); + + verify(transportManager, times(1)) + .log( + traceArgumentCaptor.capture(), + ArgumentMatchers.nullable(ApplicationProcessState.class)); + TraceMetric metric = traceArgumentCaptor.getValue(); + Assert.assertEquals(Constants.TraceNames.APP_START_TRACE_NAME.toString(), metric.getName()); + Assert.assertEquals("globalValue", metric.getCustomAttributesMap().get("globalKey")); + + firebasePerformance.removeAttribute("globalKey"); + } } diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilderTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilderTest.java index 61b3823741d..d0656d95af9 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilderTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilderTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.MockitoAnnotations.initMocks; +import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.FirebasePerformanceTestBase; import com.google.firebase.perf.application.AppStateMonitor; import com.google.firebase.perf.session.PerfSession; @@ -323,6 +324,34 @@ public void testSettingNullForContentTypeClearsIt() { assertThat(networkMetricBuilder.build().hasResponseContentType()).isFalse(); } + @Test + public void testGlobalAttributesIncludedInNetworkRequestMetric() { + FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); + firebasePerformance.putAttribute("globalKey", "globalValue"); + + networkMetricBuilder.setCustomAttributes(java.util.Collections.emptyMap()); + + NetworkRequestMetric metric = networkMetricBuilder.build(); + assertThat(metric.getCustomAttributesMap()).containsEntry("globalKey", "globalValue"); + + firebasePerformance.removeAttribute("globalKey"); + } + + @Test + public void testPerRequestAttributeOverridesGlobalAttributeInNetworkRequestMetric() { + FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); + firebasePerformance.putAttribute("sharedKey", "globalValue"); + + networkMetricBuilder.setCustomAttributes( + java.util.Collections.singletonMap("sharedKey", "requestValue")); + + NetworkRequestMetric metric = networkMetricBuilder.build(); + assertThat(metric.getCustomAttributesMap()).containsEntry("sharedKey", "requestValue"); + assertThat(metric.getCustomAttributesCount()).isEqualTo(1); + + firebasePerformance.removeAttribute("sharedKey"); + } + @Test public void testUpdateSessionWithValidSessionIsAdded() { networkMetricBuilder.setRequestStartTimeMicros(/* time= */ 2000); diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/TraceMetricBuilderTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/TraceMetricBuilderTest.java index fc019c51ce0..757414dddfc 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/TraceMetricBuilderTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/TraceMetricBuilderTest.java @@ -17,6 +17,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.MockitoAnnotations.initMocks; +import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.FirebasePerformanceTestBase; import com.google.firebase.perf.application.AppStateMonitor; import com.google.firebase.perf.transport.TransportManager; @@ -331,4 +332,61 @@ public void testUpdatingCustomAttributes() { Assert.assertEquals(TRACE_1, traceMetric.getName()); Assert.assertEquals(1, traceMetric.getCustomAttributesCount()); } + + @Test + public void testGlobalAttributesIncludedInTraceMetric() { + FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); + firebasePerformance.putAttribute("globalKey", "globalValue"); + + Trace trace = new Trace(TRACE_1, transportManager, clock, appStateMonitor); + currentTime = 1; + trace.start(); + currentTime = 2; + trace.stop(); + TraceMetric traceMetric = new TraceMetricBuilder(trace).build(); + + Assert.assertEquals(1, traceMetric.getCustomAttributesCount()); + Assert.assertEquals("globalValue", traceMetric.getCustomAttributesMap().get("globalKey")); + + firebasePerformance.removeAttribute("globalKey"); + } + + @Test + public void testTraceAttributeOverridesGlobalAttribute() { + FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); + firebasePerformance.putAttribute("sharedKey", "globalValue"); + + Trace trace = new Trace(TRACE_1, transportManager, clock, appStateMonitor); + currentTime = 1; + trace.start(); + trace.putAttribute("sharedKey", "traceValue"); + currentTime = 2; + trace.stop(); + TraceMetric traceMetric = new TraceMetricBuilder(trace).build(); + + Assert.assertEquals(1, traceMetric.getCustomAttributesCount()); + Assert.assertEquals("traceValue", traceMetric.getCustomAttributesMap().get("sharedKey")); + + firebasePerformance.removeAttribute("sharedKey"); + } + + @Test + public void testGlobalAndTraceAttributesMerged() { + FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); + firebasePerformance.putAttribute("globalKey", "globalValue"); + + Trace trace = new Trace(TRACE_1, transportManager, clock, appStateMonitor); + currentTime = 1; + trace.start(); + trace.putAttribute("traceKey", "traceValue"); + currentTime = 2; + trace.stop(); + TraceMetric traceMetric = new TraceMetricBuilder(trace).build(); + + Assert.assertEquals(2, traceMetric.getCustomAttributesCount()); + Assert.assertEquals("globalValue", traceMetric.getCustomAttributesMap().get("globalKey")); + Assert.assertEquals("traceValue", traceMetric.getCustomAttributesMap().get("traceKey")); + + firebasePerformance.removeAttribute("globalKey"); + } } diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java index 5376265aa0e..318f14bdcfc 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java @@ -1102,6 +1102,48 @@ public void syncLogForGaugeMetric_performanceDisabled_noInteractionWithFirebaseI // region Global Custom Attributes Behaviour + @Test + public void logBuiltInTraces_globalCustomAttributesAreAdded() { + FirebasePerformance.getInstance().removeAttribute("experiment_id"); + FirebasePerformance.getInstance().removeAttribute("user_tier"); + FirebasePerformance.getInstance().putAttribute("experiment_id", "exp_123"); + FirebasePerformance.getInstance().putAttribute("user_tier", "gold"); + + // 1. App Start Trace (_as) + TraceMetric appStartTrace = createValidTraceMetric().toBuilder().setName("_as").build(); + testTransportManager.log(appStartTrace); + fakeExecutorService.runAll(); + PerfMetric loggedAppStart = getLastLoggedEvent(times(1)); + assertThat(loggedAppStart.getApplicationInfo().getCustomAttributesMap()) + .containsEntry("experiment_id", "exp_123"); + clearLastLoggedEvents(); + + // 2. Screen Trace (_st_MainActivity) + TraceMetric screenTrace = + createValidTraceMetric().toBuilder() + .setName("_st_MainActivity") + .putCounters(Constants.CounterNames.FRAMES_TOTAL.toString(), 100L) + .build(); + testTransportManager.log(screenTrace); + fakeExecutorService.runAll(); + PerfMetric loggedScreenTrace = getLastLoggedEvent(times(1)); + assertThat(loggedScreenTrace.getApplicationInfo().getCustomAttributesMap()) + .containsEntry("user_tier", "gold"); + clearLastLoggedEvents(); + + // 3. Network Request + NetworkRequestMetric networkMetric = createValidNetworkRequestMetric(); + testTransportManager.log(networkMetric); + fakeExecutorService.runAll(); + PerfMetric loggedNetworkMetric = getLastLoggedEvent(times(1)); + assertThat(loggedNetworkMetric.getApplicationInfo().getCustomAttributesMap()) + .containsEntry("experiment_id", "exp_123"); + + // Cleanup + FirebasePerformance.getInstance().removeAttribute("experiment_id"); + FirebasePerformance.getInstance().removeAttribute("user_tier"); + } + @Test public void logTraceMetric_globalCustomAttributesAreAdded() { FirebasePerformance.getInstance().putAttribute("test_key1", "test_value1"); From c8e65023267d6e4c9ec1955aef13458c036dc387 Mon Sep 17 00:00:00 2001 From: Joseph Rodiz Date: Mon, 11 May 2026 16:45:05 -0600 Subject: [PATCH 2/4] Address PR #8031 review comments: expand changelog to reflect full attribute scope Global attributes apply to all traces (built-in and custom) via TraceMetricBuilder, not only the built-in traces listed in the original changelog entry. Updated the entry to accurately describe the full scope and mention that the attribute methods and constants were made public. --- firebase-perf/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-perf/CHANGELOG.md b/firebase-perf/CHANGELOG.md index 03e8cf74f54..a4ce9d82756 100644 --- a/firebase-perf/CHANGELOG.md +++ b/firebase-perf/CHANGELOG.md @@ -1,6 +1,6 @@ # Unreleased -- [feature] Added support for custom attributes on built-in traces. Global attributes set via `FirebasePerformance.getInstance().putAttribute()` are now included in all built-in traces (`_app_start`, `_app_in_foreground`, `_app_in_background`, screen traces) and network requests. [#6664] +- [feature] Made global attribute methods (`putAttribute`, `removeAttribute`, `getAttribute`, `getAttributes`) and related constants public on `FirebasePerformance`. Global attributes are now merged into all traces — built-in (`_app_start`, `_app_in_foreground`, `_app_in_background`, screen traces) and custom — as well as all network requests. Trace- or request-level attributes override global attributes on key conflicts. [#6664] - [changed] Bumped internal dependencies. # 22.0.4 From 083702f939bcbf3ee6acdbf173e1d5e94aea746a Mon Sep 17 00:00:00 2001 From: Joseph Rodiz Date: Wed, 13 May 2026 23:57:04 -0600 Subject: [PATCH 3/4] Remove redundant attribute merging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TransportManager.setApplicationInfoAndBuild already adds global custom attributes to all traces and network requests at the ApplicationInfo level (TransportManager.java:507-512). The merging logic added in this PR to TraceMetricBuilder, NetworkRequestMetricBuilder, AppStartTrace, and AppStateMonitor duplicated those attributes at the TraceMetric / NetworkRequestMetric level, which is redundant. Visibility changes on FirebasePerformance, api.txt, and CHANGELOG are preserved — exposing the previously @hide global-attribute APIs is all that this PR needs to do. --- .../perf/application/AppStateMonitor.java | 7 --- .../firebase/perf/metrics/AppStartTrace.java | 7 --- .../metrics/NetworkRequestMetricBuilder.java | 11 +--- .../perf/metrics/TraceMetricBuilder.java | 11 +--- .../perf/application/AppStateMonitorTest.java | 61 ------------------- .../perf/metrics/AppStartTraceTest.java | 29 --------- .../NetworkRequestMetricBuilderTest.java | 29 --------- .../perf/metrics/TraceMetricBuilderTest.java | 58 ------------------ 8 files changed, 2 insertions(+), 211 deletions(-) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/application/AppStateMonitor.java b/firebase-perf/src/main/java/com/google/firebase/perf/application/AppStateMonitor.java index dd6e04ff084..b014d82bb83 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/application/AppStateMonitor.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/application/AppStateMonitor.java @@ -22,7 +22,6 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.FragmentActivity; -import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.logging.AndroidLogger; import com.google.firebase.perf.metrics.FrameMetricsCalculator.PerfFrameMetrics; @@ -392,12 +391,6 @@ private void sendSessionLog(String name, Timer startTime, Timer endTime) { // reset metrics. metricToCountMap.clear(); } - try { - metric.putAllCustomAttributes(FirebasePerformance.getInstance().getAttributes()); - } catch (IllegalStateException e) { - // FirebaseApp not initialized yet, skip global attributes - } - // The Foreground and Background trace marks the transition between the two states, // so we always specify the state to be ApplicationProcessState.FOREGROUND_BACKGROUND. transportManager.log(metric.build(), ApplicationProcessState.FOREGROUND_BACKGROUND); diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java index 212546c78c6..813c8988383 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java @@ -35,7 +35,6 @@ import androidx.lifecycle.ProcessLifecycleOwner; import com.google.firebase.FirebaseApp; import com.google.firebase.StartupTime; -import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.logging.AndroidLogger; import com.google.firebase.perf.session.PerfSession; @@ -479,12 +478,6 @@ private void logAppStartTrace() { metric.addAllSubtraces(subtraces).addPerfSessions(this.startSession.build()); - try { - metric.putAllCustomAttributes(FirebasePerformance.getInstance().getAttributes()); - } catch (IllegalStateException e) { - // FirebaseApp not initialized yet, skip global attributes - } - transportManager.log(metric.build(), ApplicationProcessState.FOREGROUND_BACKGROUND); } diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilder.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilder.java index 9c442b45010..1e04744d1b2 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilder.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilder.java @@ -18,7 +18,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.application.AppStateMonitor; import com.google.firebase.perf.application.AppStateUpdateHandler; import com.google.firebase.perf.logging.AndroidLogger; @@ -38,7 +37,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -215,14 +213,7 @@ public NetworkRequestMetricBuilder setRequestPayloadBytes(long bytes) { /** Sets the customAttributes for the current {@link NetworkRequestMetric}. */ public NetworkRequestMetricBuilder setCustomAttributes(Map attributes) { - Map merged = new HashMap<>(); - try { - merged.putAll(FirebasePerformance.getInstance().getAttributes()); - } catch (IllegalStateException e) { - // FirebaseApp not initialized yet, skip global attributes - } - merged.putAll(attributes); - builder.clearCustomAttributes().putAllCustomAttributes(merged); + builder.clearCustomAttributes().putAllCustomAttributes(attributes); return this; } diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/TraceMetricBuilder.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/TraceMetricBuilder.java index ceb75740829..424ef2c5d45 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/TraceMetricBuilder.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/TraceMetricBuilder.java @@ -15,11 +15,9 @@ package com.google.firebase.perf.metrics; import androidx.annotation.NonNull; -import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.session.PerfSession; import com.google.firebase.perf.v1.TraceMetric; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -55,14 +53,7 @@ TraceMetric build() { } } - Map mergedAttributes = new HashMap<>(); - try { - mergedAttributes.putAll(FirebasePerformance.getInstance().getAttributes()); - } catch (IllegalStateException e) { - // FirebaseApp not initialized yet, skip global attributes - } - mergedAttributes.putAll(trace.getAttributes()); - traceMetric.putAllCustomAttributes(mergedAttributes); + traceMetric.putAllCustomAttributes(trace.getAttributes()); com.google.firebase.perf.v1.PerfSession[] perfSessions = PerfSession.buildAndSort(trace.getSessions()); diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java index 5ed3d509dfb..0b7d4bbfc17 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java @@ -33,7 +33,6 @@ import android.content.Context; import android.os.Bundle; import android.view.WindowManager.LayoutParams; -import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.FirebasePerformanceInitializer; import com.google.firebase.perf.FirebasePerformanceTestBase; import com.google.firebase.perf.config.ConfigResolver; @@ -772,66 +771,6 @@ public void appColdStart_singleSubscriberRegistersForMultipleTimes_oneCallbackIs verify(mockInitializer1, times(1)).onAppColdStart(); } - @Test - public void screenTrace_globalAttributesIncluded() { - FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); - firebasePerformance.putAttribute("globalKey", "globalValue"); - - AppStateMonitor monitor = new AppStateMonitor(transportManager, clock); - currentTime = 1; - monitor.onActivityStarted(activity1); - currentTime = 2; - monitor.onActivityStopped(activity1); - - verify(transportManager, times(1)) - .log(argTraceMetric.capture(), any(ApplicationProcessState.class)); - TraceMetric metric = argTraceMetric.getValue(); - assertThat(metric.getName()).startsWith(Constants.SCREEN_TRACE_PREFIX); - assertThat(metric.getCustomAttributesMap()).containsEntry("globalKey", "globalValue"); - - firebasePerformance.removeAttribute("globalKey"); - } - - @Test - public void foregroundTrace_globalAttributesIncluded() { - FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); - firebasePerformance.putAttribute("globalKey", "globalValue"); - - AppStateMonitor monitor = new AppStateMonitor(transportManager, clock); - currentTime = 1; - monitor.onActivityResumed(activity1); - currentTime = 2; - monitor.onActivityStopped(activity1); - - verify(transportManager, times(1)).log(argTraceMetric.capture(), eq(FOREGROUND_BACKGROUND)); - TraceMetric metric = argTraceMetric.getValue(); - Assert.assertEquals(Constants.TraceNames.FOREGROUND_TRACE_NAME.toString(), metric.getName()); - Assert.assertEquals("globalValue", metric.getCustomAttributesMap().get("globalKey")); - - firebasePerformance.removeAttribute("globalKey"); - } - - @Test - public void backgroundTrace_globalAttributesIncluded() { - FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); - firebasePerformance.putAttribute("globalKey", "globalValue"); - - AppStateMonitor monitor = new AppStateMonitor(transportManager, clock); - currentTime = 1; - monitor.onActivityResumed(activity1); - currentTime = 2; - monitor.onActivityStopped(activity1); - currentTime = 3; - monitor.onActivityResumed(activity1); - - verify(transportManager, times(2)).log(argTraceMetric.capture(), eq(FOREGROUND_BACKGROUND)); - TraceMetric metric = argTraceMetric.getValue(); - Assert.assertEquals(Constants.TraceNames.BACKGROUND_TRACE_NAME.toString(), metric.getName()); - Assert.assertEquals("globalValue", metric.getCustomAttributesMap().get("globalKey")); - - firebasePerformance.removeAttribute("globalKey"); - } - private static Activity createFakeActivity(boolean isHardwareAccelerated) { ActivityController fakeActivityController = Robolectric.buildActivity(Activity.class); diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java index b34f9925bde..daa80ad29a2 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java @@ -33,7 +33,6 @@ import android.os.SystemClock; import android.view.View; import androidx.test.core.app.ApplicationProvider; -import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.FirebasePerformanceTestBase; import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.session.PerfSession; @@ -342,32 +341,4 @@ public void timeToInitialDisplay_isLogged() { assertThat(ttid.getDurationUs()).isEqualTo(drawTime - appStartTime); assertThat(ttid.getSubtracesCount()).isEqualTo(3); } - - @Test - public void testAppStartTrace_globalAttributesIncluded() { - FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); - firebasePerformance.putAttribute("globalKey", "globalValue"); - - FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService(); - AppStartTrace trace = - new AppStartTrace(transportManager, clock, configResolver, fakeExecutorService); - trace.registerActivityLifecycleCallbacks(appContext); - currentTime = 1; - trace.onActivityCreated(activity1, bundle); - currentTime = 2; - trace.onActivityStarted(activity1); - currentTime = 3; - trace.onActivityResumed(activity1); - fakeExecutorService.runAll(); - - verify(transportManager, times(1)) - .log( - traceArgumentCaptor.capture(), - ArgumentMatchers.nullable(ApplicationProcessState.class)); - TraceMetric metric = traceArgumentCaptor.getValue(); - Assert.assertEquals(Constants.TraceNames.APP_START_TRACE_NAME.toString(), metric.getName()); - Assert.assertEquals("globalValue", metric.getCustomAttributesMap().get("globalKey")); - - firebasePerformance.removeAttribute("globalKey"); - } } diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilderTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilderTest.java index d0656d95af9..61b3823741d 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilderTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilderTest.java @@ -20,7 +20,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.MockitoAnnotations.initMocks; -import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.FirebasePerformanceTestBase; import com.google.firebase.perf.application.AppStateMonitor; import com.google.firebase.perf.session.PerfSession; @@ -324,34 +323,6 @@ public void testSettingNullForContentTypeClearsIt() { assertThat(networkMetricBuilder.build().hasResponseContentType()).isFalse(); } - @Test - public void testGlobalAttributesIncludedInNetworkRequestMetric() { - FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); - firebasePerformance.putAttribute("globalKey", "globalValue"); - - networkMetricBuilder.setCustomAttributes(java.util.Collections.emptyMap()); - - NetworkRequestMetric metric = networkMetricBuilder.build(); - assertThat(metric.getCustomAttributesMap()).containsEntry("globalKey", "globalValue"); - - firebasePerformance.removeAttribute("globalKey"); - } - - @Test - public void testPerRequestAttributeOverridesGlobalAttributeInNetworkRequestMetric() { - FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); - firebasePerformance.putAttribute("sharedKey", "globalValue"); - - networkMetricBuilder.setCustomAttributes( - java.util.Collections.singletonMap("sharedKey", "requestValue")); - - NetworkRequestMetric metric = networkMetricBuilder.build(); - assertThat(metric.getCustomAttributesMap()).containsEntry("sharedKey", "requestValue"); - assertThat(metric.getCustomAttributesCount()).isEqualTo(1); - - firebasePerformance.removeAttribute("sharedKey"); - } - @Test public void testUpdateSessionWithValidSessionIsAdded() { networkMetricBuilder.setRequestStartTimeMicros(/* time= */ 2000); diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/TraceMetricBuilderTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/TraceMetricBuilderTest.java index 757414dddfc..fc019c51ce0 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/TraceMetricBuilderTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/TraceMetricBuilderTest.java @@ -17,7 +17,6 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.MockitoAnnotations.initMocks; -import com.google.firebase.perf.FirebasePerformance; import com.google.firebase.perf.FirebasePerformanceTestBase; import com.google.firebase.perf.application.AppStateMonitor; import com.google.firebase.perf.transport.TransportManager; @@ -332,61 +331,4 @@ public void testUpdatingCustomAttributes() { Assert.assertEquals(TRACE_1, traceMetric.getName()); Assert.assertEquals(1, traceMetric.getCustomAttributesCount()); } - - @Test - public void testGlobalAttributesIncludedInTraceMetric() { - FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); - firebasePerformance.putAttribute("globalKey", "globalValue"); - - Trace trace = new Trace(TRACE_1, transportManager, clock, appStateMonitor); - currentTime = 1; - trace.start(); - currentTime = 2; - trace.stop(); - TraceMetric traceMetric = new TraceMetricBuilder(trace).build(); - - Assert.assertEquals(1, traceMetric.getCustomAttributesCount()); - Assert.assertEquals("globalValue", traceMetric.getCustomAttributesMap().get("globalKey")); - - firebasePerformance.removeAttribute("globalKey"); - } - - @Test - public void testTraceAttributeOverridesGlobalAttribute() { - FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); - firebasePerformance.putAttribute("sharedKey", "globalValue"); - - Trace trace = new Trace(TRACE_1, transportManager, clock, appStateMonitor); - currentTime = 1; - trace.start(); - trace.putAttribute("sharedKey", "traceValue"); - currentTime = 2; - trace.stop(); - TraceMetric traceMetric = new TraceMetricBuilder(trace).build(); - - Assert.assertEquals(1, traceMetric.getCustomAttributesCount()); - Assert.assertEquals("traceValue", traceMetric.getCustomAttributesMap().get("sharedKey")); - - firebasePerformance.removeAttribute("sharedKey"); - } - - @Test - public void testGlobalAndTraceAttributesMerged() { - FirebasePerformance firebasePerformance = FirebasePerformance.getInstance(); - firebasePerformance.putAttribute("globalKey", "globalValue"); - - Trace trace = new Trace(TRACE_1, transportManager, clock, appStateMonitor); - currentTime = 1; - trace.start(); - trace.putAttribute("traceKey", "traceValue"); - currentTime = 2; - trace.stop(); - TraceMetric traceMetric = new TraceMetricBuilder(trace).build(); - - Assert.assertEquals(2, traceMetric.getCustomAttributesCount()); - Assert.assertEquals("globalValue", traceMetric.getCustomAttributesMap().get("globalKey")); - Assert.assertEquals("traceValue", traceMetric.getCustomAttributesMap().get("traceKey")); - - firebasePerformance.removeAttribute("globalKey"); - } } From d6a91bec4ae174234955507a79f2bbe991ecbed8 Mon Sep 17 00:00:00 2001 From: Joseph Rodiz Date: Thu, 14 May 2026 00:20:15 -0600 Subject: [PATCH 4/4] Update CHANGELOG entry to reflect attribute-attachment location --- firebase-perf/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-perf/CHANGELOG.md b/firebase-perf/CHANGELOG.md index a4ce9d82756..f7877ed2f1b 100644 --- a/firebase-perf/CHANGELOG.md +++ b/firebase-perf/CHANGELOG.md @@ -1,6 +1,6 @@ # Unreleased -- [feature] Made global attribute methods (`putAttribute`, `removeAttribute`, `getAttribute`, `getAttributes`) and related constants public on `FirebasePerformance`. Global attributes are now merged into all traces — built-in (`_app_start`, `_app_in_foreground`, `_app_in_background`, screen traces) and custom — as well as all network requests. Trace- or request-level attributes override global attributes on key conflicts. [#6664] +- [feature] Made the global attribute methods (`putAttribute`, `removeAttribute`, `getAttribute`, `getAttributes`) and related constants (`MAX_TRACE_CUSTOM_ATTRIBUTES`, `MAX_ATTRIBUTE_KEY_LENGTH`, `MAX_ATTRIBUTE_VALUE_LENGTH`) public on `FirebasePerformance`. Global attributes set via these methods are attached to all traces — built-in (`_app_start`, `_app_in_foreground`, `_app_in_background`, screen traces) and custom — as well as all network requests. [#6664] - [changed] Bumped internal dependencies. # 22.0.4