Skip to content

Instantly share code, notes, and snippets.

@alexamies
Last active April 6, 2022 12:46
Show Gist options
  • Save alexamies/037d7c11432ea2301de9995515c6deea to your computer and use it in GitHub Desktop.
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.
/**
* 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;
});
}
/**
* 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';
/**
* 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);
}
}
/**
* 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;
}
}
@efenderbosch
Copy link

I think line 80 of lib/LogCollector.ts should be using message instead of msg

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment