Skip to content

Commit

Permalink
Attach JS logger in Website only
Browse files Browse the repository at this point in the history
  • Loading branch information
bgrgicak committed Feb 28, 2024
1 parent 1d840ed commit a3feb32
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 138 deletions.
300 changes: 166 additions & 134 deletions packages/php-wasm/logger/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UniversalPHP } from "@php-wasm/universal/src/lib/universal-php";
import { UniversalPHP } from '@php-wasm/universal/src/lib/universal-php';
/**
* Log severity levels.
*/
Expand All @@ -8,149 +8,181 @@ export type LogSeverity = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
* A logger for Playground.
*/
export class Logger {
private readonly LOG_PREFIX = 'Playground';

/**
* The length of the last PHP log.
*/
private lastPHPLogLength = 0;

/**
* The path to the error log file.
*/
private errorLogPath = '/wordpress/wp-content/debug.log';

constructor(errorLogPath?: string) {
if (errorLogPath) {
this.errorLogPath = errorLogPath;
}
this.collectJavaScriptErrors();
}

/**
* Collect errors from JavaScript window events like error and log them.
*/
private collectJavaScriptErrors() {
if (typeof window !== 'undefined') {
window.addEventListener('error', (event) => {
this.log(
`${event.message} in ${event.filename} on line ${event.lineno}:${event.colno}`,
'fatal'
);
});
window.addEventListener('unhandledrejection', (event) => {
this.log(
`${event.reason.stack}`,
'fatal'
);
});
window.addEventListener('rejectionhandled', (event) => {
this.log(
`${event.reason.stack}`,
'fatal'
);
});
}
}

/**
* Read the WordPress debug.log file and return its content.
*
* @param UniversalPHP playground instance
* @returns string The content of the debug.log file
*/
private readonly LOG_PREFIX = 'Playground';

/**
* Whether the window events are connected.
*/
private windowConnected = false;

/**
* The length of the last PHP log.
*/
private lastPHPLogLength = 0;

/**
* The path to the error log file.
*/
private errorLogPath = '/wordpress/wp-content/debug.log';

constructor(errorLogPath?: string) {
if (errorLogPath) {
this.errorLogPath = errorLogPath;
}
}

/**
* Read the WordPress debug.log file and return its content.
*
* @param UniversalPHP playground instance
* @returns string The content of the debug.log file
*/
private async getRequestPhpErrorLog(playground: UniversalPHP) {
if (!await playground.fileExists(this.errorLogPath)) {
if (!(await playground.fileExists(this.errorLogPath))) {
return '';
}
return await playground.readFileAsText(this.errorLogPath);
}
/**
* Register a listener for the request.end event and log the data.
* @param UniversalPHP playground instance
*/
public addPlaygroundRequestEndListener(playground: UniversalPHP) {
playground.addEventListener('request.end', async () => {
const log = await this.getRequestPhpErrorLog(playground);
if (log.length > this.lastPHPLogLength) {
this.logRaw(log.substring(this.lastPHPLogLength));
this.lastPHPLogLength = log.length;
}
} );
}


/**
* Get UTC date in the PHP log format https://github.com/php/php-src/blob/master/main/main.c#L849
*
* @param date
* @returns string
*/
private formatLogDate(date: Date): string {
const formattedDate = new Intl.DateTimeFormat('en-GB', {
year: 'numeric',
month: 'short',
day: '2-digit',
timeZone: 'UTC',
}).format(date).replace(/ /g, '-');

const formattedTime = new Intl.DateTimeFormat('en-GB', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
timeZone: 'UTC',
timeZoneName: 'short'
}).format(date);
return formattedDate + ' ' + formattedTime;
}

/**
* Format log message and severity and log it.
* @param string message
* @param LogSeverity severity
*/
public formatMessage(message: string, severity: LogSeverity): string {
const now = this.formatLogDate(new Date());
return `[${now}] ${this.LOG_PREFIX} ${severity}: ${message}`;
}

/**
* Log message with severity and timestamp.
* @param string message
* @param LogSeverity severity
*/
public log(message: string, severity?: LogSeverity): void {
if (severity === undefined) {
severity = 'info';
}
const log = this.formatMessage(message, severity);
this.logRaw(log);
}

/**
* Log message without severity and timestamp.
* @param string log
*/
public logRaw(log: string): void {
console.debug(log);
}
};

/**
* Log Windows errors.
*
* @param ErrorEvent event
*/
private logWindowError(event: ErrorEvent) {
this.log(
`${event.message} in ${event.filename} on line ${event.lineno}:${event.colno}`,
'fatal'
);
}

/**
* Log unhandled promise rejections.
*
* @param PromiseRejectionEvent event
*/
private logUnhandledRejection(event: PromiseRejectionEvent) {
this.log(`${event.reason.stack}`, 'fatal');
}

/**
* Register a listener for the window error events and log the data.
*/
public addWindowErrorListener() {
// Ensure that the window events are connected only once.
if (this.windowConnected) {
return;
}
if (typeof window === 'undefined') {
return;
}

window.addEventListener('error', this.logWindowError.bind(this));
window.addEventListener(
'unhandledrejection',
this.logUnhandledRejection.bind(this)
);
window.addEventListener(
'rejectionhandled',
this.logUnhandledRejection.bind(this)
);
this.windowConnected = true;
}

/**
* Register a listener for the request.end event and log the data.
* @param UniversalPHP playground instance
*/
public addPlaygroundRequestEndListener(playground: UniversalPHP) {
playground.addEventListener('request.end', async () => {
const log = await this.getRequestPhpErrorLog(playground);
if (log.length > this.lastPHPLogLength) {
this.logRaw(log.substring(this.lastPHPLogLength));
this.lastPHPLogLength = log.length;
}
});
}

/**
* Get UTC date in the PHP log format https://github.com/php/php-src/blob/master/main/main.c#L849
*
* @param date
* @returns string
*/
private formatLogDate(date: Date): string {
const formattedDate = new Intl.DateTimeFormat('en-GB', {
year: 'numeric',
month: 'short',
day: '2-digit',
timeZone: 'UTC',
})
.format(date)
.replace(/ /g, '-');

const formattedTime = new Intl.DateTimeFormat('en-GB', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
timeZone: 'UTC',
timeZoneName: 'short',
}).format(date);
return formattedDate + ' ' + formattedTime;
}

/**
* Format log message and severity and log it.
* @param string message
* @param LogSeverity severity
*/
public formatMessage(message: string, severity: LogSeverity): string {
const now = this.formatLogDate(new Date());
return `[${now}] ${this.LOG_PREFIX} ${severity}: ${message}`;
}

/**
* Log message with severity and timestamp.
* @param string message
* @param LogSeverity severity
*/
public log(message: string, severity?: LogSeverity): void {
if (severity === undefined) {
severity = 'info';
}
const log = this.formatMessage(message, severity);
this.logRaw(log);
}

/**
* Log message without severity and timestamp.
* @param string log
*/
public logRaw(log: string): void {
console.debug(log);
}
}

/**
* The logger instance.
*/
let logger: Logger | undefined = undefined;
export const logger: Logger = new Logger();

/**
* Collect errors from JavaScript window events like error and log them.
* @param loggerInstance The logger instance
*/
export function collectWindowErrors(loggerInstance: Logger) {
loggerInstance.addWindowErrorListener();
}

/**
* Get the logger instance.
*
* @param errorLogPath The path to the error log file.
* @returns Logger
* Collect PHP logs from the error_log file and log them.
* @param UniversalPHP playground instance
* @param loggerInstance The logger instance
*/
export function getLogger(errorLogPath?: string): Logger {
if (!logger) {
logger = new Logger(errorLogPath);
}
return logger;
export function collectPhpLogs(
loggerInstance: Logger,
playground: UniversalPHP
) {
loggerInstance.addPlaygroundRequestEndListener(playground);
}
4 changes: 2 additions & 2 deletions packages/playground/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
import { consumeAPI } from '@php-wasm/web';
import { ProgressTracker } from '@php-wasm/progress';
import { PlaygroundClient } from '@wp-playground/remote';
import { getLogger } from '@php-wasm/logger';
import { collectPhpLogs, logger } from '@php-wasm/logger';
export interface StartPlaygroundOptions {
iframe: HTMLIFrameElement;
remoteUrl: string;
Expand Down Expand Up @@ -95,7 +95,7 @@ export async function startPlaygroundWeb({
}),
progressTracker
);
getLogger().addPlaygroundRequestEndListener(playground);
collectPhpLogs(logger, playground);
await runBlueprintSteps(compiled, playground);
progressTracker.finish();

Expand Down
4 changes: 2 additions & 2 deletions packages/playground/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from '@wp-playground/blueprints';
import { NodePHP } from '@php-wasm/node';
import { UniversalPHP } from '@php-wasm/universal';
import { getLogger } from '@php-wasm/logger';
import { collectPhpLogs, logger } from '@php-wasm/logger';

export interface NodePlaygroundOptions {
blueprint?: Blueprint;
Expand Down Expand Up @@ -44,7 +44,7 @@ export async function startPlaygroundNode(
},
});

getLogger().addPlaygroundRequestEndListener(playground);
collectPhpLogs(logger, playground);

await defineSiteUrl(playground, {
siteUrl: options.serverUrl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { usePlayground } from '../../lib/hooks';
import { StorageType } from '../../types';
import PlaygroundContext from './context';
import { logTrackingEvent } from '../../lib/tracking';
import { collectWindowErrors, logger } from '@php-wasm/logger';

export const supportedDisplayModes = [
'browser',
Expand Down Expand Up @@ -49,6 +50,10 @@ export default function PlaygroundViewport({
}
}, [blueprint?.steps]);

useEffect(() => {
collectWindowErrors(logger);
}, []);

return (
<PlaygroundContext.Provider
value={{
Expand Down

0 comments on commit a3feb32

Please sign in to comment.