Skip to content

Commit 77d9558

Browse files
committed
Immediately flush logs when page enters "hidden" state
1 parent d5eaa45 commit 77d9558

File tree

7 files changed

+75
-23
lines changed

7 files changed

+75
-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: 35 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

@@ -96,6 +101,12 @@ describe('helpers', () => {
96101
value: originalCrypto,
97102
writable: true
98103
});
104+
if (!isNode()) {
105+
Object.defineProperty(document, 'visibilityState', {
106+
value: 'visible',
107+
writable: true
108+
});
109+
}
99110
delete AUTO_CONSTANTS.appVersion;
100111
});
101112

@@ -136,4 +147,26 @@ describe('helpers', () => {
136147
});
137148
});
138149
});
150+
151+
describe('registerListeners', () => {
152+
if (isNode()) {
153+
it('should do nothing in node', () => {
154+
registerListeners(fakeTelemetry);
155+
});
156+
} else {
157+
it('should flush logs when the page is hidden', () => {
158+
registerListeners(fakeTelemetry);
159+
160+
expect(flushed).to.be.false;
161+
162+
Object.defineProperty(document, 'visibilityState', {
163+
value: 'hidden',
164+
writable: true
165+
});
166+
window.dispatchEvent(new Event('visibilitychange'));
167+
168+
expect(flushed).to.be.true;
169+
});
170+
}
171+
});
139172
});

packages/telemetry/src/helpers.ts

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

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)