Skip to content

Commit 4277e4c

Browse files
authored
Client logging API: methods for users to access the SDK's log messages (#2434)
1 parent 000316f commit 4277e4c

File tree

13 files changed

+570
-44
lines changed

13 files changed

+570
-44
lines changed

packages/analytics/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"dependencies": {
2727
"@firebase/analytics-types": "0.2.8",
2828
"@firebase/installations": "0.4.5",
29+
"@firebase/logger": "0.1.37",
2930
"@firebase/util": "0.2.42",
3031
"@firebase/component": "0.1.7",
3132
"tslib": "1.11.1"

packages/analytics/src/helpers.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
GTAG_URL
3232
} from './constants';
3333
import { FirebaseInstallations } from '@firebase/installations-types';
34+
import { logger } from './logger';
3435

3536
/**
3637
* Initialize the analytics instance in gtag.js by calling config command with fid.
@@ -146,7 +147,7 @@ function wrapGtag(
146147
gtagParams || {}
147148
)
148149
)
149-
.catch(e => console.error(e));
150+
.catch(e => logger.error(e));
150151
} else if (command === GtagCommand.CONFIG) {
151152
const initializationPromiseToWait =
152153
initializedIdPromisesMap[idOrNameOrParams as string] ||
@@ -155,7 +156,7 @@ function wrapGtag(
155156
.then(() => {
156157
gtagCore(GtagCommand.CONFIG, idOrNameOrParams as string, gtagParams);
157158
})
158-
.catch(e => console.error(e));
159+
.catch(e => logger.error(e));
159160
} else {
160161
// SET command.
161162
// Splitting calls for CONFIG and SET to make it clear which signature

packages/analytics/src/logger.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @license
3+
* Copyright 2019 Google Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { Logger } from '@firebase/logger';
19+
20+
export const logger = new Logger('@firebase/analytics');

packages/app-types/index.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17+
import { LogCallback, LogLevelString, LogOptions } from '@firebase/logger';
1718

1819
export type FirebaseOptions = {
1920
apiKey?: string;
@@ -105,6 +106,12 @@ export interface FirebaseNamespace {
105106
*/
106107
registerVersion(library: string, version: string, variant?: string): void;
107108

109+
// Sets log level for all Firebase components.
110+
setLogLevel(logLevel: LogLevelString): void;
111+
112+
// Sets log handler for all Firebase components.
113+
onLog(logCallback: LogCallback, options?: LogOptions): void;
114+
108115
// The current SDK version.
109116
SDK_VERSION: string;
110117
}

packages/app/src/errors.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ export const enum AppError {
2222
BAD_APP_NAME = 'bad-app-name',
2323
DUPLICATE_APP = 'duplicate-app',
2424
APP_DELETED = 'app-deleted',
25-
INVALID_APP_ARGUMENT = 'invalid-app-argument'
25+
INVALID_APP_ARGUMENT = 'invalid-app-argument',
26+
INVALID_LOG_ARGUMENT = 'invalid-log-argument'
2627
}
2728

2829
const ERRORS: ErrorMap<AppError> = {
@@ -34,7 +35,8 @@ const ERRORS: ErrorMap<AppError> = {
3435
[AppError.APP_DELETED]: "Firebase App named '{$appName}' already deleted",
3536
[AppError.INVALID_APP_ARGUMENT]:
3637
'firebase.{$appName}() takes either no argument or a ' +
37-
'Firebase App instance.'
38+
'Firebase App instance.',
39+
[AppError.INVALID_LOG_ARGUMENT]: 'First argument to `onLog` must be null or a function.'
3840
};
3941

4042
type ErrorParams = { [key in AppError]: { appName: string } };

packages/app/src/firebaseNamespaceCore.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ import { FirebaseAppLiteImpl } from './lite/firebaseAppLite';
3434
import { DEFAULT_ENTRY_NAME, PLATFORM_LOG_STRING } from './constants';
3535
import { version } from '../../firebase/package.json';
3636
import { logger } from './logger';
37+
import {
38+
setUserLogHandler,
39+
setLogLevel,
40+
LogCallback,
41+
LogOptions
42+
} from '@firebase/logger';
3743
import { Component, ComponentType, Name } from '@firebase/component';
3844

3945
/**
@@ -60,6 +66,8 @@ export function createFirebaseNamespaceCore(
6066
// @ts-ignore
6167
app,
6268
registerVersion,
69+
setLogLevel,
70+
onLog,
6371
// @ts-ignore
6472
apps: null,
6573
SDK_VERSION: version,
@@ -277,6 +285,15 @@ export function createFirebaseNamespaceCore(
277285
);
278286
}
279287

288+
function onLog(logCallback: LogCallback | null, options?: LogOptions): void {
289+
if (logCallback !== null && typeof logCallback !== 'function') {
290+
throw ERROR_FACTORY.create(AppError.INVALID_LOG_ARGUMENT, {
291+
appName: name
292+
});
293+
}
294+
setUserLogHandler(logCallback, options);
295+
}
296+
280297
// Map the requested service to a registered service name
281298
// (used to map auth to serverAuth service when needed).
282299
function useAsService(app: FirebaseApp, name: string): string | null {
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* @license
3+
* Copyright 2017 Google Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { FirebaseNamespace, VersionService } from '@firebase/app-types';
19+
import { _FirebaseApp, _FirebaseNamespace } from '@firebase/app-types/private';
20+
import { createFirebaseNamespace } from '../src/firebaseNamespace';
21+
import { expect } from 'chai';
22+
import { spy as Spy } from 'sinon';
23+
import './setup';
24+
import { Logger } from '@firebase/logger';
25+
import { registerCoreComponents } from '../src/registerCoreComponents';
26+
import {
27+
Component,
28+
ComponentType
29+
} from '@firebase/component';
30+
31+
declare module '@firebase/component' {
32+
interface NameServiceMapping {
33+
'vs1': VersionService;
34+
'vs2': VersionService;
35+
'test-shell': Promise<void>;
36+
}
37+
}
38+
39+
describe('User Log Methods', () => {
40+
describe('Integration Tests', () => {
41+
let firebase: FirebaseNamespace;
42+
let result: any = null;
43+
const warnSpy = Spy(console, 'warn');
44+
const infoSpy = Spy(console, 'info');
45+
const logSpy = Spy(console, 'log');
46+
47+
beforeEach(() => {
48+
firebase = createFirebaseNamespace();
49+
});
50+
51+
it(`respects log level set through firebase.setLogLevel()`, () => {
52+
firebase.initializeApp({});
53+
registerCoreComponents(firebase);
54+
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
55+
new Component(
56+
'test-shell',
57+
async () => {
58+
const logger = new Logger('@firebase/logger-test');
59+
logger.warn('hello');
60+
expect(warnSpy.called).to.be.true;
61+
(firebase as _FirebaseNamespace).setLogLevel('warn');
62+
logger.info('hi');
63+
expect(infoSpy.called).to.be.false;
64+
logger.log('hi');
65+
expect(logSpy.called).to.be.false;
66+
logSpy.resetHistory();
67+
infoSpy.resetHistory();
68+
(firebase as _FirebaseNamespace).setLogLevel('debug');
69+
logger.info('hi');
70+
expect(infoSpy.called).to.be.true;
71+
logger.log('hi');
72+
expect(logSpy.called).to.be.true;
73+
},
74+
ComponentType.PUBLIC
75+
)
76+
);
77+
return (firebase as any)['test-shell']();
78+
});
79+
80+
it(`correctly triggers callback given to firebase.onLog()`, () => {
81+
// Note: default log level is INFO.
82+
firebase.initializeApp({});
83+
registerCoreComponents(firebase);
84+
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
85+
new Component(
86+
'test-shell',
87+
async () => {
88+
const logger = new Logger('@firebase/logger-test');
89+
(firebase as _FirebaseNamespace).onLog((logData) => {
90+
result = logData;
91+
});
92+
logger.info('hi');
93+
expect(result.level).to.equal('info');
94+
expect(result.message).to.equal('hi');
95+
expect(result.args).to.deep.equal(['hi']);
96+
expect(result.type).to.equal('@firebase/logger-test');
97+
expect(infoSpy.called).to.be.true;
98+
},
99+
ComponentType.PUBLIC
100+
)
101+
);
102+
return (firebase as any)['test-shell']();
103+
});
104+
});
105+
});

packages/firebase/index.d.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ declare namespace firebase {
7878
complete: CompleteFn;
7979
}
8080

81+
/**
82+
* The JS SDK supports 5 log levels and also allows a user the ability to
83+
* silence the logs altogether.
84+
*
85+
* The order is as follows:
86+
* silent < debug < verbose < info < warn < error
87+
*/
88+
type LogLevel = 'debug' | 'verbose' | 'info' | 'warn' | 'error' | 'silent';
89+
8190
/**
8291
* The current SDK version.
8392
*/
@@ -95,6 +104,49 @@ declare namespace firebase {
95104
variant?: string
96105
): void;
97106

107+
/**
108+
* Sets log level for all Firebase packages.
109+
*
110+
* All of the log types above the current log level are captured (i.e. if
111+
* you set the log level to `info`, errors are logged, but `debug` and
112+
* `verbose` logs are not).
113+
*/
114+
function setLogLevel(logLevel: LogLevel): void;
115+
116+
/**
117+
* Sets log handler for all Firebase packages.
118+
* @param logCallback An optional custom log handler that executes user code whenever
119+
* the Firebase SDK makes a logging call.
120+
*/
121+
function onLog(
122+
logCallback: (callbackParams: {
123+
/**
124+
* Level of event logged.
125+
*/
126+
level: LogLevel;
127+
/**
128+
* Any text from logged arguments joined into one string.
129+
*/
130+
message: string;
131+
/**
132+
* The raw arguments passed to the log call.
133+
*/
134+
args: unknown[];
135+
/**
136+
* A string indicating the name of the package that made the log call,
137+
* such as `@firebase/firestore`.
138+
*/
139+
type: string;
140+
}) => void,
141+
options?: {
142+
/**
143+
* Threshhold log level. Only logs at or above this level trigger the `logCallback`
144+
* passed to `onLog`.
145+
*/
146+
level: LogLevel;
147+
}
148+
): void;
149+
98150
/**
99151
* @hidden
100152
*/

packages/logger/index.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { instances, LogLevel } from './src/logger';
19-
20-
export function setLogLevel(level: LogLevel): void {
21-
instances.forEach(inst => {
22-
inst.logLevel = level;
23-
});
24-
}
25-
26-
export { Logger, LogLevel, LogHandler } from './src/logger';
18+
export {
19+
setLogLevel,
20+
Logger,
21+
LogLevel,
22+
LogHandler,
23+
setUserLogHandler,
24+
LogCallback,
25+
LogLevelString,
26+
LogOptions
27+
} from './src/logger';

packages/logger/karma.conf.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,14 @@
1515
* limitations under the License.
1616
*/
1717

18-
const karma = require('karma');
19-
const path = require('path');
2018
const karmaBase = require('../../config/karma.base');
2119

2220
const files = [`test/**/*`];
2321

2422
module.exports = function(config) {
2523
const karmaConfig = Object.assign({}, karmaBase, {
2624
// files to load into karma
27-
files: files,
25+
files,
2826
// frameworks to use
2927
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
3028
frameworks: ['mocha']

0 commit comments

Comments
 (0)