Skip to content

@firebase/logger #473

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Feb 28, 2018
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1eaa3fc
Start work on @firebase/logger package
jshcrowthe Jan 17, 2018
26a9fd0
Refactor to remove debug dep
jshcrowthe Jan 25, 2018
07ff3ff
[AUTOMATED]: Prettier Code Styling
jshcrowthe Jan 25, 2018
0895b3d
[AUTOMATED]: License Headers
jshcrowthe Jan 25, 2018
12118c0
Expose a LogHandler type
jshcrowthe Jan 25, 2018
d3a4b6b
Allow users to "silence" our logging
jshcrowthe Jan 29, 2018
e0e2ecb
Adding some comments
jshcrowthe Jan 29, 2018
3d497db
Do @firebase/firestore integration
jshcrowthe Jan 29, 2018
1add931
Do @firebase/database integration
jshcrowthe Jan 29, 2018
c7b8557
[AUTOMATED]: Prettier Code Styling
jshcrowthe Jan 29, 2018
51d519b
Refactor to propagate the default level if changed
jshcrowthe Jan 29, 2018
67e8f35
Add some basic tests
jshcrowthe Jan 30, 2018
6128011
[AUTOMATED]: Prettier Code Styling
jshcrowthe Jan 30, 2018
f369890
Feedback from @mikelehen and @schmidt-sebastian
jshcrowthe Feb 20, 2018
d90daab
[AUTOMATED]: Prettier Code Styling
jshcrowthe Feb 21, 2018
36105b9
Fixing an error message
jshcrowthe Feb 21, 2018
2ba4043
Updating yarn.lock
jshcrowthe Feb 26, 2018
1d027ed
Address @mikelehen feedback
jshcrowthe Feb 26, 2018
9c766dc
[AUTOMATED]: Prettier Code Styling
jshcrowthe Feb 26, 2018
53a96fe
Refactor logClient.log -> logClient.debug
jshcrowthe Feb 26, 2018
056610b
Update deps
jshcrowthe Feb 26, 2018
4c01a1b
More @mikelehen feedback
jshcrowthe Feb 28, 2018
3ec3d8a
Fixing comment
jshcrowthe Feb 28, 2018
8b0c7e4
Feedback from @schmidt-sebastian
jshcrowthe Feb 28, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/database/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
},
"dependencies": {
"@firebase/database-types": "0.1.2",
"@firebase/logger": "0.1.0",
"@firebase/util": "0.1.10",
"faye-websocket": "0.11.1",
"tslib": "^1.9.0"
Expand Down
2 changes: 1 addition & 1 deletion packages/database/src/core/PersistentConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -935,7 +935,7 @@ export class PersistentConnection extends ServerActions {
if (this.securityDebugCallback_) {
this.securityDebugCallback_(body);
} else {
if ('msg' in body && typeof console !== 'undefined') {
if ('msg' in body) {
console.log('FIREBASE: ' + body['msg'].replace('\n', '\nFIREBASE: '));
}
}
Expand Down
39 changes: 11 additions & 28 deletions packages/database/src/core/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import { stringToByteArray } from '@firebase/util';
import { stringify } from '@firebase/util';
import { SessionStorage } from '../storage/storage';
import { isNodeSdk } from '@firebase/util';
import { Logger } from '@firebase/logger';

const logClient = new Logger('@firebase/database');

/**
* Returns a locally-unique ID (generated by just incrementing up from 0 each time its called).
Expand Down Expand Up @@ -105,16 +108,7 @@ export const enableLogging = function(
"Can't turn on custom loggers persistently."
);
if (logger_ === true) {
if (typeof console !== 'undefined') {
if (typeof console.log === 'function') {
logger = console.log.bind(console);
} else if (typeof console.log === 'object') {
// IE does this.
logger = function(message) {
console.log(message);
};
}
}
logger = logClient.log.bind(logClient);
if (persistent) SessionStorage.set('logging_enabled', true);
} else if (typeof logger_ === 'function') {
logger = logger_;
Expand Down Expand Up @@ -157,36 +151,25 @@ export const logWrapper = function(
* @param {...string} var_args
*/
export const error = function(...var_args: string[]) {
if (typeof console !== 'undefined') {
const message = 'FIREBASE INTERNAL ERROR: ' + buildLogMessage_(...var_args);
if (typeof console.error !== 'undefined') {
console.error(message);
} else {
console.log(message);
}
}
const message = 'FIREBASE INTERNAL ERROR: ' + buildLogMessage_(...var_args);
logClient.error(message);
};

/**
* @param {...string} var_args
*/
export const fatal = function(...var_args: string[]) {
const message = buildLogMessage_(...var_args);
throw new Error('FIREBASE FATAL ERROR: ' + message);
const message = `FIREBASE FATAL ERROR: ${buildLogMessage_(...var_args)}`;
logClient.error(message);
throw new Error(message);
};

/**
* @param {...*} var_args
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is for the hidden block just above (line 173): I feel that we should also log 'fatal' messages to the user-specified log functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

*/
export const warn = function(...var_args: any[]) {
if (typeof console !== 'undefined') {
const message = 'FIREBASE WARNING: ' + buildLogMessage_(...var_args);
if (typeof console.warn !== 'undefined') {
console.warn(message);
} else {
console.log(message);
}
}
const message = 'FIREBASE WARNING: ' + buildLogMessage_(...var_args);
logClient.warn(message);
};

/**
Expand Down
1 change: 1 addition & 0 deletions packages/firestore/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"license": "Apache-2.0",
"dependencies": {
"@firebase/firestore-types": "0.2.2",
"@firebase/logger": "0.1.0",
"@firebase/webchannel-wrapper": "0.2.6",
"grpc": "^1.9.1",
"tslib": "^1.9.0"
Expand Down
42 changes: 32 additions & 10 deletions packages/firestore/src/util/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,58 @@
import { SDK_VERSION } from '../core/version';
import { AnyJs } from './misc';
import { PlatformSupport } from '../platform/platform';
import { Logger, LogLevel as FirebaseLogLevel } from '@firebase/logger';

const logClient = new Logger('@firebase/firestore');

export enum LogLevel {
DEBUG,
ERROR,
SILENT
}

let logLevel = LogLevel.ERROR;

// Helper methods are needed because variables can't be exported as read/write
export function getLogLevel(): LogLevel {
return logLevel;
if (logClient.logLevel === FirebaseLogLevel.DEBUG) {
return LogLevel.DEBUG;
} else if (logClient.logLevel === FirebaseLogLevel.SILENT) {
return LogLevel.SILENT;
} else {
return LogLevel.ERROR;
}
}
export function setLogLevel(newLevel: LogLevel): void {
logLevel = newLevel;
/**
* Map the new log level to the associated Firebase Log Level
*/
switch (newLevel) {
case LogLevel.DEBUG:
logClient.logLevel = FirebaseLogLevel.DEBUG;
break;
case LogLevel.ERROR:
logClient.logLevel = FirebaseLogLevel.ERROR;
break;
case LogLevel.SILENT:
logClient.logLevel = FirebaseLogLevel.SILENT;
break;
default:
logClient.error(
`Firestore (${SDK_VERSION}): Invalid value passed to \`setLogLevel\``
);
}
}

export function debug(tag: string, msg: string, ...obj: AnyJs[]): void {
if (logLevel <= LogLevel.DEBUG) {
const time = new Date().toISOString();
if (logClient.logLevel <= FirebaseLogLevel.DEBUG) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you intentionally comparing to DEBUG and then using .log() below [as opposed to .debug]? I'm guessing this should be .LOG here, but then we also need to change the setLogLevel() code above to map Firestore's DEBUG LogLevel to Firebase's LOG LogLevel...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch here, refactored the logging call to logClient.debug

const args = obj.map(argToString);
console.log(`Firestore (${SDK_VERSION}) ${time} [${tag}]: ${msg}`, ...args);
logClient.debug(`Firestore (${SDK_VERSION}) [${tag}]: ${msg}`, ...args);
}
}

export function error(msg: string, ...obj: AnyJs[]): void {
if (logLevel <= LogLevel.ERROR) {
const time = new Date().toISOString();
if (logClient.logLevel <= FirebaseLogLevel.ERROR) {
const args = obj.map(argToString);
console.error(`Firestore (${SDK_VERSION}) ${time}: ${msg}`, ...args);
logClient.error(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/logger/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This file is left intentionally blank
39 changes: 39 additions & 0 deletions packages/logger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# @firebase/logger

This package serves as the base of all logging in the JS SDK. Any logging that
is intended to be for firebase end developers, should go through this module.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comma and maybe reword as:

This package serves as the base of all logging in the JS SDK. Any logging that
is intended to be visible to Firebase end developers should go through this module.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


## Basic Usage

Firebase components should import the `Logger` class and instantiate a new
instance by passing a component name (e.g. `@firebase/<COMPONENT>`) to the
constructor.

_e.g._

```typescript
import { Logger } from '@firebase/logger';

const logClient = new Logger(`@firebase/<COMPONENT>`);
```

Each `Logger` instance supports 5 log functions each to be used in a specific
instance:

- `debug`: Internal logs; use this to allow developers to send us their debug
logs for us to be able to diagnose an issue.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So console.debug() logs are disabled by default in Chrome Dev Tools (the developer needs to explicitly change the log filter to "verbose.") This means that to get these logs from a developer we would need to tell them to set the LogLevel to DEBUG and change the filter in chrome dev tools. We don't want to make them do two steps (and in general there's no reason to have two gates for enabling / disabling our logs).

So I think we should avoid using console.debug() entirely. It'll just cause confusion. This means we need to either drop LogLevel.DEBUG or make it use console.log() [in which case we should drop LogLevel.LOG]. My preference is for dropping LogLevel.LOG.

Copy link
Contributor Author

@jshcrowthe jshcrowthe Feb 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left a comment in logger.ts to explain this and I am now intentionally routing debug logs to console.log.

I feel like there is still a valid distinction to be made between LogLevel.INFO and LogLevel.LOG.

Some strawmen for examples:

LogLevel.INFO: If a user doesn't initialize an app with all of the config required to init all the services (think, if we add a new field to that config), this would be a good INFO candidate. It's not immediately breaking, and a user has an action item they can take to alleviate the issue.

LogLevel.LOG: If a user is trying to use some feature that has suffers from unsolvable performance issues in Hybrid environments, we may Log a message notifying the user that there is some known slowness.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's an action item for the user, wouldn't we want the log on by default? INFO logs are off by default, so that would not be appropriate.

But my bigger problem is that "LOG" is meaningless since these are all logs and I've never come across another logging system that has "LOG" as its own log level so nobody will know what this means or where it fits in the overall ordering. Even other JavaScript loggers (where log as a loglevel might almost make sense because of the console.log legacy) don't have it...

winston [10k stars] - error, warn, info, verbose, debug
bunyan [5k stars] - fatal, error, warn, info, debug, trace
loglevel [1k stars] - error, warn, info, debug, trace

So I appreciate that you've made up your own level and have strawmen examples to back it up, but I think it would be better to stick with well-known ones.

I think in practice:

  1. 90% of customers will stick with the default (warn and error) all the time.
  2. 10% of customers may enable debug logging because support asked them to or they found a StackOverflow / forum post that mentions it.
  3. 0% of customers will care to distinguish between info/log/debug.
  4. Only Firestore and RTDB use these logs and they only have error, warn, and debug. So there's currently no distinction to make anyway.

All that said, I don't really care. I think we should avoid LOG. If you think we need 5 log levels instead of 4 or 3, then please pick from the existing set of log levels that other systems use (notice, verbose, trace, etc.) for your 5th. Keep in mind we could always add more log levels later if we need them.

- `log`: Use to inform your user about things they may need to know.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned, I'd like to drop "log" but if we keep it, can you make this description clearer? Is "your user" the Firebase end developer? And what does "may need to know" mean? Perhaps an example would help.

- `info`: Use if you have to inform the user about something that they need to
take a concrete action on. Once they take that action, the log should go away.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The info logs are off by default and so this description doesn't sound right. In my mind, info would be informational info about what the SDK is doing (e.g. maybe a log message for each network request... but not as verbose as debug-level logging).

- `warn`: Use when a product feature may stop functioning correctly; unexpected
scenario. Majority of user-facing logs.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would merge the current info explanation ("inform the user about something that they need to take a concrete action on. Once they take that action, the log should go away.") into here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd take out "Majority of user-facing logs.". This is likely only true when the user doesn't enable debug logging.

- `error`: Only use when user App would stop functioning correctly - super rare!

## Log Format

Each log will be formatted in the following manner:

```typescript
`[${new Date()}] ${COMPONENT_NAME}: ${...args}`
```

31 changes: 31 additions & 0 deletions packages/logger/gulpfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const gulp = require('gulp');
const tools = require('../../tools/build');

const buildModule = gulp.parallel([
tools.buildCjs(__dirname),
tools.buildEsm(__dirname)
]);

const setupWatcher = () => {
gulp.watch(['index.ts', 'src/**/*'], buildModule);
};

gulp.task('build', buildModule);

gulp.task('dev', gulp.parallel([setupWatcher]));
25 changes: 25 additions & 0 deletions packages/logger/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { instances, LogLevel } from './src/logger';

export function setLogLevel(level: LogLevel) {
instances.forEach(inst => {
inst.logLevel = level;
});
}

export { Logger, LogLevel, LogHandler } from './src/logger';
31 changes: 31 additions & 0 deletions packages/logger/karma.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const karma = require('karma');
const path = require('path');
const karmaBase = require('../../config/karma.base');

module.exports = function(config) {
const karmaConfig = Object.assign({}, karmaBase, {
// files to load into karma
files: [{ pattern: `test/**/*` }],
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha']
});

config.set(karmaConfig);
};
54 changes: 54 additions & 0 deletions packages/logger/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "@firebase/logger",
"version": "0.1.0",
"private": true,
"description": "A logger package for use in the Firebase JS SDK",
"author": "Firebase <[email protected]> (https://firebase.google.com/)",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"scripts": {
"dev": "gulp dev",
"test": "run-p test:browser test:node",
"test:browser": "karma start --single-run",
"test:browser:debug": "karma start --browsers Chrome --auto-watch",
"test:node": "nyc --reporter lcovonly -- mocha test/**/*.test.* --compilers ts:ts-node/register --exit",
"prepare": "gulp build"
},
"license": "Apache-2.0",
"devDependencies": {
"@types/chai": "^4.1.2",
"@types/mocha": "^2.2.48",
"@types/sinon": "^4.1.3",
"chai": "^4.1.1",
"gulp": "^4.0.0",
"karma": "^2.0.0",
"karma-chrome-launcher": "^2.2.0",
"karma-cli": "^1.0.1",
"karma-mocha": "^1.3.0",
"karma-sauce-launcher": "^1.2.0",
"karma-spec-reporter": "^0.0.32",
"karma-webpack": "^2.0.9",
"mocha": "^5.0.1",
"npm-run-all": "^4.1.1",
"nyc": "^11.4.1",
"ts-loader": "^3.5.0",
"ts-node": "^5.0.0",
"typescript": "^2.7.2",
"webpack": "^3.11.0"
},
"repository": {
"type": "git",
"url": "https://github.com/firebase/firebase-js-sdk/tree/master/packages/logger"
},
"bugs": {
"url": "https://github.com/firebase/firebase-js-sdk/issues"
},
"typings": "dist/esm/index.d.ts",
"nyc": {
"extension": [
".ts"
],
"reportDir": "./coverage/node"
},
"dependencies": {}
}
Loading