Skip to content

Commit 07916b3

Browse files
authored
Immediately flush logs when page enters "hidden" state (#9418)
* Immediately flush logs when page enters "hidden" state * Also flush on the pagehide event
1 parent d5eaa45 commit 07916b3

File tree

7 files changed

+90
-23
lines changed

7 files changed

+90
-23
lines changed

common/api-review/telemetry-angular.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { FirebaseApp } from '@firebase/app';
99

1010
// @public
1111
export class FirebaseErrorHandler implements ErrorHandler {
12-
constructor(app: FirebaseApp, telemetryOptions?: TelemetryOptions | undefined);
12+
constructor(app: FirebaseApp, telemetryOptions?: TelemetryOptions);
1313
// (undocumented)
1414
handleError(error: unknown): void;
1515
}

docs-devsite/telemetry_angular.firebaseerrorhandler.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,15 @@ Constructs a new instance of the `FirebaseErrorHandler` class
4040
<b>Signature:</b>
4141
4242
```typescript
43-
constructor(app: FirebaseApp, telemetryOptions?: TelemetryOptions | undefined);
43+
constructor(app: FirebaseApp, telemetryOptions?: TelemetryOptions);
4444
```
4545
4646
#### Parameters
4747
4848
| Parameter | Type | Description |
4949
| --- | --- | --- |
5050
| app | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | |
51-
| telemetryOptions | [TelemetryOptions](./telemetry_.telemetryoptions.md#telemetryoptions_interface) \| undefined | |
51+
| telemetryOptions | [TelemetryOptions](./telemetry_.telemetryoptions.md#telemetryoptions_interface) | |
5252
5353
## FirebaseErrorHandler.handleError()
5454

packages/telemetry/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
"@angular/platform-browser": "19.2.15",
112112
"@angular/router": "19.2.15",
113113
"@firebase/app": "0.14.6",
114+
"@firebase/util": "1.13.0",
114115
"@opentelemetry/sdk-trace-web": "2.1.0",
115116
"@rollup/plugin-json": "6.1.0",
116117
"@rollup/plugin-replace": "6.0.2",

packages/telemetry/src/api.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { Provider } from '@firebase/component';
2222
import { AnyValueMap, SeverityNumber } from '@opentelemetry/api-logs';
2323
import { trace } from '@opentelemetry/api';
2424
import { TelemetryService } from './service';
25-
import { getAppVersion, getSessionId } from './helpers';
25+
import { flush, getAppVersion, getSessionId } from './helpers';
2626
import { TelemetryInternal } from './types';
2727

2828
declare module '@firebase/component' {
@@ -132,19 +132,4 @@ export function captureError(
132132
}
133133
}
134134

135-
/**
136-
* Flushes all enqueued telemetry data immediately, instead of waiting for default batching.
137-
*
138-
* @public
139-
*
140-
* @param telemetry - The {@link Telemetry} instance.
141-
* @returns a promise which is resolved when all flushes are complete
142-
*/
143-
export function flush(telemetry: Telemetry): Promise<void> {
144-
// Cast to TelemetryInternal to access internal loggerProvider
145-
return (telemetry as TelemetryInternal).loggerProvider
146-
.forceFlush()
147-
.catch(err => {
148-
console.error('Error flushing logs from Firebase Telemetry:', err);
149-
});
150-
}
135+
export { flush };

packages/telemetry/src/helpers.test.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
import { expect } from 'chai';
1919
import { LoggerProvider } from '@opentelemetry/sdk-logs';
2020
import { Logger, LogRecord } from '@opentelemetry/api-logs';
21-
import { startNewSession } from './helpers';
21+
import { isNode } from '@firebase/util';
22+
import { registerListeners, startNewSession } from './helpers';
2223
import {
2324
LOG_ENTRY_ATTRIBUTE_KEYS,
2425
TELEMETRY_SESSION_ID_KEY
@@ -34,6 +35,7 @@ describe('helpers', () => {
3435
let originalCrypto: Crypto | undefined;
3536
let storage: Record<string, string> = {};
3637
let emittedLogs: LogRecord[] = [];
38+
let flushed = false;
3739

3840
const fakeLoggerProvider = {
3941
getLogger: (): Logger => {
@@ -43,7 +45,10 @@ describe('helpers', () => {
4345
}
4446
};
4547
},
46-
forceFlush: () => Promise.resolve(),
48+
forceFlush: () => {
49+
flushed = true;
50+
return Promise.resolve();
51+
},
4752
shutdown: () => Promise.resolve()
4853
} as unknown as LoggerProvider;
4954

@@ -61,6 +66,7 @@ describe('helpers', () => {
6166

6267
beforeEach(() => {
6368
emittedLogs = [];
69+
flushed = false;
6470
storage = {};
6571
// @ts-ignore
6672
originalSessionStorage = global.sessionStorage;
@@ -96,6 +102,12 @@ describe('helpers', () => {
96102
value: originalCrypto,
97103
writable: true
98104
});
105+
if (!isNode()) {
106+
Object.defineProperty(document, 'visibilityState', {
107+
value: 'visible',
108+
writable: true
109+
});
110+
}
99111
delete AUTO_CONSTANTS.appVersion;
100112
});
101113

@@ -136,4 +148,36 @@ describe('helpers', () => {
136148
});
137149
});
138150
});
151+
152+
describe('registerListeners', () => {
153+
if (isNode()) {
154+
it('should do nothing in node', () => {
155+
registerListeners(fakeTelemetry);
156+
});
157+
} else {
158+
it('should flush logs when the visibility changes to hidden', () => {
159+
registerListeners(fakeTelemetry);
160+
161+
expect(flushed).to.be.false;
162+
163+
Object.defineProperty(document, 'visibilityState', {
164+
value: 'hidden',
165+
writable: true
166+
});
167+
window.dispatchEvent(new Event('visibilitychange'));
168+
169+
expect(flushed).to.be.true;
170+
});
171+
172+
it('should flush logs when the pagehide event fires', () => {
173+
registerListeners(fakeTelemetry);
174+
175+
expect(flushed).to.be.false;
176+
177+
window.dispatchEvent(new Event('pagehide'));
178+
179+
expect(flushed).to.be.true;
180+
});
181+
}
182+
});
139183
});

packages/telemetry/src/helpers.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,37 @@ export function startNewSession(telemetry: Telemetry): void {
8181
}
8282
}
8383
}
84+
85+
/**
86+
* Registers event listeners to flush logs when the page is hidden. In some cases multiple listeners
87+
* may trigger at the same time, but flushing only occurs once per batch.
88+
*/
89+
export function registerListeners(telemetry: Telemetry): void {
90+
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
91+
window.addEventListener('visibilitychange', async () => {
92+
if (document.visibilityState === 'hidden') {
93+
await flush(telemetry);
94+
}
95+
});
96+
window.addEventListener('pagehide', async () => {
97+
await flush(telemetry);
98+
});
99+
}
100+
}
101+
102+
/**
103+
* Flushes all enqueued telemetry data immediately, instead of waiting for default batching.
104+
*
105+
* @public
106+
*
107+
* @param telemetry - The {@link Telemetry} instance.
108+
* @returns a promise which is resolved when all flushes are complete
109+
*/
110+
export function flush(telemetry: Telemetry): Promise<void> {
111+
// Cast to TelemetryInternal to access internal loggerProvider
112+
return (telemetry as TelemetryInternal).loggerProvider
113+
.forceFlush()
114+
.catch(err => {
115+
console.error('Error flushing logs from Firebase Telemetry:', err);
116+
});
117+
}

packages/telemetry/src/register.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import { createLoggerProvider } from './logging/logger-provider';
2323
import { AppCheckProvider } from './logging/appcheck-provider';
2424
import { InstallationIdProvider } from './logging/installation-id-provider';
2525
import { TELEMETRY_TYPE } from './constants';
26+
import { getSessionId, registerListeners, startNewSession } from './helpers';
2627

2728
// We only import types from this package elsewhere in the `telemetry` package, so this
2829
// explicit import is needed here to prevent this module from being tree-shaken out.
2930
import '@firebase/installations';
30-
import { getSessionId, startNewSession } from './helpers';
3131

3232
export function registerTelemetry(): void {
3333
_registerComponent(
@@ -65,6 +65,9 @@ export function registerTelemetry(): void {
6565
startNewSession(telemetryService);
6666
}
6767

68+
// Register relevant event listeners
69+
registerListeners(telemetryService);
70+
6871
return telemetryService;
6972
},
7073
ComponentType.PUBLIC

0 commit comments

Comments
 (0)