Last active
April 6, 2022 12:46
-
-
Save alexamies/037d7c11432ea2301de9995515c6deea to your computer and use it in GitHub Desktop.
A TypeScript module to demonstrate redefining console.log with a function that sends the logs to the server.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 { fromEvent } from "rxjs"; | |
import { LogCollector, LogCollectorBuilder } from "./index.js"; | |
/** | |
* An application to demonstrate used of the module to send console.log logs | |
* to the server. | |
*/ | |
// Start the log collector | |
const buildId = "v0.01"; | |
const builder = new LogCollectorBuilder().setBuildId(buildId).setReplaceConsole(true); | |
const logCollector = builder.makeLogCollector(); | |
logCollector.start(); | |
// Example UI elements to generate some logs | |
// Handle events for the name form | |
const nameForm = document.getElementById("nameForm"); | |
const nameTF = document.getElementById("nameTF") as HTMLInputElement; | |
if (nameForm) { | |
const events = fromEvent(nameForm, "submit"); | |
events.subscribe( (event) => { | |
event.preventDefault(); | |
const name = nameTF.value as string; | |
logCollector.log(`Your name is ${name}`); | |
return false; | |
}); | |
} | |
// Handle events for the favorite color form | |
const favColorForm = document.getElementById("favColorForm"); | |
const favColorTF = document.getElementById("favColorTF") as HTMLInputElement; | |
if (favColorForm) { | |
const events = fromEvent(favColorForm, "submit"); | |
events.subscribe( (event) => { | |
event.preventDefault(); | |
const favColor = favColorTF.value as string; | |
console.log(`Your favorite color is ${favColor}`); | |
// recklessly generate a null pointer error | |
let nullValue: string | null = ""; | |
nullValue = null; | |
nullValue!.toUpperCase(); | |
return false; | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright 2019, 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. | |
*/ | |
/** | |
* A module to demonstrate redefining console.log with a function that | |
* sends the logs to the server. | |
*/ | |
export { LogCollectorBuilder } from './lib/LogCollectorBuilder'; | |
export { LogCollector } from './lib/LogCollector'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright 2019, 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 { of } from "rxjs"; | |
import { ajax } from "rxjs/ajax"; | |
import { catchError, map } from "rxjs/operators"; | |
const defaultLog = console.log.bind(console); | |
const defaultError = console.error.bind(console); | |
/** | |
* Class to collect log and error messages into buffers which are sent to the | |
* server at regular intervals. It will replace the console.log() and | |
* console.error() methods, if configured to do so. | |
*/ | |
export class LogCollector { | |
private buildId: string; | |
private logs = new Array<string>(); | |
private errors = new Array<string>(); | |
/** | |
* Use LogCollectorBuilder to creates a LogCollector object | |
*/ | |
constructor(buildId: string, replaceConsole: boolean) { | |
this.buildId = buildId; | |
if (replaceConsole) { | |
console.log = (msg: string, ...args: object[]) => { | |
if (args && args.length) { | |
defaultLog(msg, args); | |
} else { | |
defaultLog(msg); | |
} | |
this.log(msg, args); | |
}; | |
} | |
// const defaultError = console.error.bind(console); | |
if (replaceConsole) { | |
console.error = (msg: string, ...args: object[]) => { | |
if (args && args.length) { | |
defaultError(msg, args); | |
} else { | |
defaultError(msg); | |
} | |
this.error(msg, args); | |
}; | |
} | |
// Listen for global errors | |
window.onerror = (msg, url, lineNo, columnNo, error) => { | |
if (error && error.stack) { | |
this.errors.push(`Uncaught error: ${msg} in url ${url}\n${error.stack}`); | |
} else { | |
this.errors.push(`Uncaught error: ${msg}\n url ${url}\n Line: ${lineNo}`); | |
} | |
}; | |
} | |
/** | |
* Log an error | |
* @param {string} msg - The message to log | |
* @param {Array.<Object>} args - Optional parameters | |
*/ | |
public error(msg: string, ...args: object[]) { | |
let message = msg; | |
if (args) { | |
message += args.join(", "); | |
} | |
this.errors.push(`browser app ${this.buildId} error:\n${msg}`);; | |
} | |
/** | |
* Log a message | |
* @param {string} msg - The message to log | |
* @param {Array.<Object>} args - Optional parameters | |
*/ | |
public log(msg: string, ...args: object[]) { | |
let message = msg; | |
if (args) { | |
message += args.join(", "); | |
} | |
this.logs.push(`browser app ${this.buildId}: ${message}`); | |
} | |
/** | |
* Flush the logs to the server at regular intervals | |
* @param {integer} interval - The interval to flush the log | |
*/ | |
public start(interval = 10000) { | |
const logs = this.logs; | |
const errors = this.errors; | |
const flushLogs = () => { | |
const statusDiv = document.getElementById("status"); | |
if (logs && logs.length > 0 || errors && errors.length > 0) { | |
if (statusDiv) { | |
statusDiv.textContent = "Status: flushing logs"; | |
} | |
const payload = {logs, errors}; | |
const obs = ajax({ | |
body: JSON.stringify(payload), | |
headers: { | |
"Content-Type": "application/json" | |
}, | |
method: "POST", | |
url: "/log" | |
}).pipe( | |
map((response) => { | |
if (statusDiv) { | |
const lNum = logs.length; | |
const eNum = errors.length; | |
const message = `Status: flushed ${lNum} logs and ${eNum} errors`; | |
statusDiv.textContent = message; | |
} | |
}), | |
catchError((error) => { | |
console.error("error: ", error); | |
if (statusDiv) { | |
statusDiv.textContent = `Status: error flushing logs: ${error}`; | |
} | |
return of(error); | |
}) | |
); | |
obs.subscribe( | |
(val) => { | |
while (logs.pop()) { | |
// empty the logs buffer | |
} | |
while (errors.pop()) { | |
// empty the errors buffer | |
} | |
}, | |
(err) => console.error(`Flush error ${ err }`) | |
); | |
} else { | |
if (statusDiv) { | |
statusDiv.textContent = "Status: no logs or errors to flush"; | |
} | |
} | |
}; | |
setInterval(flushLogs, interval); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright 2019, 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 { LogCollector } from './LogCollector'; | |
/** | |
* Class to configure creation of a LogCollector objects. | |
*/ | |
export class LogCollectorBuilder { | |
private buildId = ""; | |
private replaceConsole = false; | |
/** | |
* Creates a new LogCollector with the given or default configuration | |
* @return The LogCollector instance created | |
*/ | |
public makeLogCollector() { | |
return new LogCollector(this.buildId, this.replaceConsole); | |
} | |
/** | |
* @return the buildId to be prepended to log messages | |
*/ | |
public getBuildId() { | |
return this.buildId; | |
} | |
/** | |
* Use the build id to identify logs from particular app versions | |
* @param {string} buildId - Prepended to the logs to identify the version | |
* @return {LogCollectorBuilder} For chaining of calls | |
*/ | |
public setBuildId(buildId: string) { | |
this.buildId = buildId; | |
return this; | |
} | |
/** | |
* @return Whether to replace console.lg() and console.error() | |
*/ | |
public getReplaceConsole() { | |
return this.replaceConsole; | |
} | |
/** | |
* Use this parameter to configure replacement of console.log() and | |
* console.error() | |
* @param {boolean} replaceConsole - Whether to replace console.log | |
* @return {LogCollectorBuilder} For chaining of calls | |
*/ | |
public setReplaceConsole(replaceConsole: boolean) { | |
this.replaceConsole = replaceConsole; | |
return this; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I think line 80 of
lib/LogCollector.ts
should be usingmessage
instead ofmsg