Integrate JS remote logging package in WooCommerce Admin (#50134)
* Update remote logger to check dev env and whether logging is enabled * Add changelog * Integrate JS remote logging package in WooCommerce Admin * Add changelog * Update remote logger package * Update test * Log error in error boundary * Update remote logger * Fix webpack config * Update log stack format * Update handleError * Add init debug
This commit is contained in:
parent
a3f4d722b9
commit
ed81aa8201
|
@ -28,7 +28,7 @@ The WooCommerce Remote Logging package offers the following features:
|
||||||
2. Log messages or errors:
|
2. Log messages or errors:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { log } from '@woocommerce/remote-logging';
|
import { log, captureException } from '@woocommerce/remote-logging';
|
||||||
|
|
||||||
// Log an informational message
|
// Log an informational message
|
||||||
log('info', 'User completed checkout', { extra: { orderId: '12345' } });
|
log('info', 'User completed checkout', { extra: { orderId: '12345' } });
|
||||||
|
@ -40,7 +40,7 @@ The WooCommerce Remote Logging package offers the following features:
|
||||||
try {
|
try {
|
||||||
// Some operation that might throw
|
// Some operation that might throw
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log('error', 'Failed to process order', { extra: { error } });
|
captureException(error, { extra: { orderId: '12345' } });
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -76,5 +76,6 @@ addFilter(
|
||||||
|
|
||||||
- `init(config: RemoteLoggerConfig): void`: Initializes the remote logger with the given configuration.
|
- `init(config: RemoteLoggerConfig): void`: Initializes the remote logger with the given configuration.
|
||||||
- `log(severity: LogSeverity, message: string, extraData?: object): Promise<void>`: Logs a message with the specified severity and optional extra data.
|
- `log(severity: LogSeverity, message: string, extraData?: object): Promise<void>`: Logs a message with the specified severity and optional extra data.
|
||||||
|
- `captureException(error: Error, extraData?: object): void`: Captures an error and sends it to the remote API.
|
||||||
|
|
||||||
For more detailed information about types and interfaces, refer to the source code and inline documentation.
|
For more detailed information about types and interfaces, refer to the source code and inline documentation.
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: update
|
||||||
|
|
||||||
|
Update remote logger to check dev env and whether logging is enabled
|
|
@ -1,6 +1,7 @@
|
||||||
export {
|
export {
|
||||||
init,
|
init,
|
||||||
log,
|
log,
|
||||||
|
captureException,
|
||||||
REMOTE_LOGGING_SHOULD_SEND_ERROR_FILTER,
|
REMOTE_LOGGING_SHOULD_SEND_ERROR_FILTER,
|
||||||
REMOTE_LOGGING_ERROR_DATA_FILTER,
|
REMOTE_LOGGING_ERROR_DATA_FILTER,
|
||||||
REMOTE_LOGGING_LOG_ENDPOINT_FILTER,
|
REMOTE_LOGGING_LOG_ENDPOINT_FILTER,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { applyFilters } from '@wordpress/hooks';
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { mergeLogData } from './utils';
|
import { mergeLogData, isDevelopmentEnvironment } from './utils';
|
||||||
import { LogData, ErrorData, RemoteLoggerConfig } from './types';
|
import { LogData, ErrorData, RemoteLoggerConfig } from './types';
|
||||||
|
|
||||||
const debug = debugFactory( 'wc:remote-logging' );
|
const debug = debugFactory( 'wc:remote-logging' );
|
||||||
|
@ -80,9 +80,38 @@ export class RemoteLogger {
|
||||||
...extraData,
|
...extraData,
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
debug( 'Logging:', logData );
|
||||||
await this.sendLog( logData );
|
await this.sendLog( logData );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs an error to Logstash.
|
||||||
|
*
|
||||||
|
* @param error - The error to log.
|
||||||
|
* @param extraData - Optional additional data to include in the log.
|
||||||
|
*
|
||||||
|
* @return {Promise<void>} - A promise that resolves when the error is logged.
|
||||||
|
*/
|
||||||
|
public async error( error: Error, extraData?: Partial< LogData > ) {
|
||||||
|
if ( this.isRateLimited() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorData: ErrorData = {
|
||||||
|
...mergeLogData( DEFAULT_LOG_DATA, {
|
||||||
|
message: error.message,
|
||||||
|
severity: 'error',
|
||||||
|
...extraData,
|
||||||
|
} ),
|
||||||
|
trace: this.getFormattedStackFrame(
|
||||||
|
TraceKit.computeStackTrace( error )
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
debug( 'Logging error:', errorData );
|
||||||
|
await this.sendError( errorData );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes error event listeners for catching unhandled errors and unhandled rejections.
|
* Initializes error event listeners for catching unhandled errors and unhandled rejections.
|
||||||
*/
|
*/
|
||||||
|
@ -115,6 +144,11 @@ export class RemoteLogger {
|
||||||
* @param logData - The log data to be sent.
|
* @param logData - The log data to be sent.
|
||||||
*/
|
*/
|
||||||
private async sendLog( logData: LogData ): Promise< void > {
|
private async sendLog( logData: LogData ): Promise< void > {
|
||||||
|
if ( isDevelopmentEnvironment ) {
|
||||||
|
debug( 'Skipping send log in development environment' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const body = new window.FormData();
|
const body = new window.FormData();
|
||||||
body.append( 'params', JSON.stringify( logData ) );
|
body.append( 'params', JSON.stringify( logData ) );
|
||||||
|
|
||||||
|
@ -142,24 +176,18 @@ export class RemoteLogger {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles an error and prepares it for sending to the remote API.
|
* Handles an uncaught error and sends it to the remote API.
|
||||||
*
|
*
|
||||||
* @param error - The error to handle.
|
* @param error - The error to handle.
|
||||||
*/
|
*/
|
||||||
private async handleError( error: Error ) {
|
private async handleError( error: Error ) {
|
||||||
const currentTime = Date.now();
|
const trace = TraceKit.computeStackTrace( error );
|
||||||
|
if ( ! this.shouldHandleError( error, trace.stack ) ) {
|
||||||
if (
|
debug( 'Irrelevant error. Skipping handling.', error );
|
||||||
currentTime - this.lastErrorSentTime <
|
|
||||||
this.config.errorRateLimitMs
|
|
||||||
) {
|
|
||||||
debug( 'Rate limit reached. Skipping send error', error );
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const trace = TraceKit.computeStackTrace( error );
|
if ( this.isRateLimited() ) {
|
||||||
if ( ! this.shouldSendError( error, trace.stack ) ) {
|
|
||||||
debug( 'Skipping error:', error );
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,12 +224,15 @@ export class RemoteLogger {
|
||||||
* @param error - The error data to be sent.
|
* @param error - The error data to be sent.
|
||||||
*/
|
*/
|
||||||
private async sendError( error: ErrorData ) {
|
private async sendError( error: ErrorData ) {
|
||||||
|
if ( isDevelopmentEnvironment ) {
|
||||||
|
debug( 'Skipping send error in development environment' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const body = new window.FormData();
|
const body = new window.FormData();
|
||||||
body.append( 'error', JSON.stringify( error ) );
|
body.append( 'error', JSON.stringify( error ) );
|
||||||
|
|
||||||
try {
|
try {
|
||||||
debug( 'Sending error to API:', error );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the JS error endpoint URL.
|
* Filters the JS error endpoint URL.
|
||||||
*
|
*
|
||||||
|
@ -212,6 +243,8 @@ export class RemoteLogger {
|
||||||
'https://public-api.wordpress.com/rest/v1.1/js-error'
|
'https://public-api.wordpress.com/rest/v1.1/js-error'
|
||||||
) as string;
|
) as string;
|
||||||
|
|
||||||
|
debug( 'Sending error to API:', error );
|
||||||
|
|
||||||
await window.fetch( endpoint, {
|
await window.fetch( endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body,
|
body,
|
||||||
|
@ -279,13 +312,13 @@ export class RemoteLogger {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether an error should be sent to the remote API.
|
* Determines whether an error should be handled.
|
||||||
*
|
*
|
||||||
* @param error - The error to check.
|
* @param error - The error to check.
|
||||||
* @param stackFrames - The stack frames of the error.
|
* @param stackFrames - The stack frames of the error.
|
||||||
* @return Whether the error should be sent.
|
* @return Whether the error should be handled.
|
||||||
*/
|
*/
|
||||||
private shouldSendError(
|
private shouldHandleError(
|
||||||
error: Error,
|
error: Error,
|
||||||
stackFrames: TraceKit.StackFrame[]
|
stackFrames: TraceKit.StackFrame[]
|
||||||
) {
|
) {
|
||||||
|
@ -310,10 +343,41 @@ export class RemoteLogger {
|
||||||
stackFrames
|
stackFrames
|
||||||
) as boolean;
|
) as boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isRateLimited(): boolean {
|
||||||
|
const currentTime = Date.now();
|
||||||
|
if (
|
||||||
|
currentTime - this.lastErrorSentTime <
|
||||||
|
this.config.errorRateLimitMs
|
||||||
|
) {
|
||||||
|
debug( 'Rate limit reached. Skipping send error' );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let logger: RemoteLogger | null = null;
|
let logger: RemoteLogger | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if remote logging is enabled and if the logger is initialized.
|
||||||
|
*
|
||||||
|
* @return {boolean} - Returns true if remote logging is enabled and the logger is initialized, otherwise false.
|
||||||
|
*/
|
||||||
|
function canLog(): boolean {
|
||||||
|
if ( ! getSetting( 'isRemoteLoggingEnabled', false ) ) {
|
||||||
|
debug( 'Remote logging is disabled.' );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! logger ) {
|
||||||
|
warnLog( 'RemoteLogger is not initialized. Call init() first.' );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the remote logging and error handlers.
|
* Initializes the remote logging and error handlers.
|
||||||
* This function should be called once at the start of the application.
|
* This function should be called once at the start of the application.
|
||||||
|
@ -321,9 +385,9 @@ let logger: RemoteLogger | null = null;
|
||||||
* @param config - Configuration object for the RemoteLogger.
|
* @param config - Configuration object for the RemoteLogger.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function init( config: RemoteLoggerConfig ): void {
|
export function init( config: RemoteLoggerConfig ) {
|
||||||
if ( ! window.wcTracks || ! window.wcTracks.isEnabled ) {
|
if ( ! getSetting( 'isRemoteLoggingEnabled', false ) ) {
|
||||||
debug( 'Tracks is not enabled.' );
|
debug( 'Remote logging is disabled.' );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,38 +399,56 @@ export function init( config: RemoteLoggerConfig ): void {
|
||||||
try {
|
try {
|
||||||
logger = new RemoteLogger( config );
|
logger = new RemoteLogger( config );
|
||||||
logger.initializeErrorHandlers();
|
logger.initializeErrorHandlers();
|
||||||
|
|
||||||
|
debug( 'RemoteLogger initialized.' );
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
errorLog( 'Failed to initialize RemoteLogger:', error );
|
errorLog( 'Failed to initialize RemoteLogger:', error );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a message or error, respecting rate limiting.
|
* Logs a message or error.
|
||||||
*
|
*
|
||||||
* This function is inefficient because the data goes over the REST API, so use sparingly.
|
* This function is inefficient because the data goes over the REST API, so use sparingly.
|
||||||
*
|
*
|
||||||
* @param severity - The severity of the log.
|
* @param severity - The severity of the log.
|
||||||
* @param message - The message to log.
|
* @param message - The message to log.
|
||||||
* @param extraData - Optional additional data to include in the log.
|
* @param extraData - Optional additional data to include in the log.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
export async function log(
|
export async function log(
|
||||||
severity: Exclude< LogData[ 'severity' ], undefined >,
|
severity: Exclude< LogData[ 'severity' ], undefined >,
|
||||||
message: string,
|
message: string,
|
||||||
extraData?: Partial< Exclude< LogData, 'message' | 'severity' > >
|
extraData?: Partial< Exclude< LogData, 'message' | 'severity' > >
|
||||||
) {
|
): Promise< void > {
|
||||||
if ( ! window.wcTracks || ! window.wcTracks.isEnabled ) {
|
if ( ! canLog() ) {
|
||||||
debug( 'Tracks is not enabled.' );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! logger ) {
|
|
||||||
warnLog( 'RemoteLogger is not initialized. Call init() first.' );
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await logger.log( severity, message, extraData );
|
await logger?.log( severity, message, extraData );
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
errorLog( 'Failed to send log:', error );
|
errorLog( 'Failed to send log:', error );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captures an error and sends it to the remote API. Respects the error rate limit.
|
||||||
|
*
|
||||||
|
* @param error - The error to capture.
|
||||||
|
* @param extraData - Optional additional data to include in the log.
|
||||||
|
*/
|
||||||
|
export async function captureException(
|
||||||
|
error: Error,
|
||||||
|
extraData?: Partial< LogData >
|
||||||
|
) {
|
||||||
|
if ( ! canLog() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await logger?.error( error, extraData );
|
||||||
|
} catch ( _error ) {
|
||||||
|
errorLog( 'Failed to send log:', _error );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,10 +3,12 @@
|
||||||
*/
|
*/
|
||||||
import '@wordpress/jest-console';
|
import '@wordpress/jest-console';
|
||||||
import { addFilter, removeFilter } from '@wordpress/hooks';
|
import { addFilter, removeFilter } from '@wordpress/hooks';
|
||||||
|
import { getSetting } from '@woocommerce/settings';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { init, log } from '../';
|
import { init, log, captureException } from '../';
|
||||||
import {
|
import {
|
||||||
RemoteLogger,
|
RemoteLogger,
|
||||||
REMOTE_LOGGING_SHOULD_SEND_ERROR_FILTER,
|
REMOTE_LOGGING_SHOULD_SEND_ERROR_FILTER,
|
||||||
|
@ -16,6 +18,12 @@ import {
|
||||||
} from '../remote-logger';
|
} from '../remote-logger';
|
||||||
import { fetchMock } from './__mocks__/fetch';
|
import { fetchMock } from './__mocks__/fetch';
|
||||||
|
|
||||||
|
jest.mock( '@woocommerce/settings', () => ( {
|
||||||
|
getSetting: jest.fn().mockReturnValue( {
|
||||||
|
isRemoteLoggingEnabled: true,
|
||||||
|
} ),
|
||||||
|
} ) );
|
||||||
|
|
||||||
jest.mock( 'tracekit', () => ( {
|
jest.mock( 'tracekit', () => ( {
|
||||||
computeStackTrace: jest.fn().mockReturnValue( {
|
computeStackTrace: jest.fn().mockReturnValue( {
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
|
@ -93,6 +101,51 @@ describe( 'RemoteLogger', () => {
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
describe( 'error', () => {
|
||||||
|
it( 'should send an error to the API with default data', async () => {
|
||||||
|
const error = new Error( 'Test error' );
|
||||||
|
await logger.error( error );
|
||||||
|
|
||||||
|
expect( fetchMock ).toHaveBeenCalledWith(
|
||||||
|
'https://public-api.wordpress.com/rest/v1.1/js-error',
|
||||||
|
expect.objectContaining( {
|
||||||
|
method: 'POST',
|
||||||
|
body: expect.any( FormData ),
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
|
||||||
|
const formData = fetchMock.mock.calls[0][1].body;
|
||||||
|
const payload = JSON.parse(formData.get('error'));
|
||||||
|
expect( payload['message'] ).toBe( 'Test error' );
|
||||||
|
expect( payload['severity'] ).toBe( 'error' );
|
||||||
|
expect( payload['trace'] ).toContain( '#1 at testFunction (http://example.com/woocommerce/assets/js/admin/app.min.js:1:1)' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should send an error to the API with extra data', async () => {
|
||||||
|
const error = new Error( 'Test error' );
|
||||||
|
const extraData = {
|
||||||
|
severity: 'warning' as const,
|
||||||
|
tags: ['custom-tag'],
|
||||||
|
};
|
||||||
|
await logger.error( error, extraData );
|
||||||
|
|
||||||
|
expect( fetchMock ).toHaveBeenCalledWith(
|
||||||
|
'https://public-api.wordpress.com/rest/v1.1/js-error',
|
||||||
|
expect.objectContaining( {
|
||||||
|
method: 'POST',
|
||||||
|
body: expect.any( FormData ),
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
|
||||||
|
const formData = fetchMock.mock.calls[0][1].body;
|
||||||
|
const payload = JSON.parse(formData.get('error'));
|
||||||
|
expect( payload['message'] ).toBe( 'Test error' );
|
||||||
|
expect( payload['severity'] ).toBe( 'warning' );
|
||||||
|
expect( payload['tags'] ).toEqual( ["woocommerce", "js", "custom-tag"]);
|
||||||
|
expect( payload['trace'] ).toContain( '#1 at testFunction (http://example.com/woocommerce/assets/js/admin/app.min.js:1:1)' );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
describe( 'handleError', () => {
|
describe( 'handleError', () => {
|
||||||
it( 'should send an error to the API', async () => {
|
it( 'should send an error to the API', async () => {
|
||||||
const error = new Error( 'Test error' );
|
const error = new Error( 'Test error' );
|
||||||
|
@ -164,7 +217,7 @@ describe( 'RemoteLogger', () => {
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
describe( 'shouldSendError', () => {
|
describe( 'shouldHandleError', () => {
|
||||||
it( 'should return true for WooCommerce errors', () => {
|
it( 'should return true for WooCommerce errors', () => {
|
||||||
const error = new Error( 'Test error' );
|
const error = new Error( 'Test error' );
|
||||||
const stackFrames = [
|
const stackFrames = [
|
||||||
|
@ -176,7 +229,7 @@ describe( 'RemoteLogger', () => {
|
||||||
column: 1,
|
column: 1,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const result = ( logger as any ).shouldSendError(
|
const result = ( logger as any ).shouldHandleError(
|
||||||
error,
|
error,
|
||||||
stackFrames
|
stackFrames
|
||||||
);
|
);
|
||||||
|
@ -194,7 +247,7 @@ describe( 'RemoteLogger', () => {
|
||||||
column: 1,
|
column: 1,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const result = ( logger as any ).shouldSendError(
|
const result = ( logger as any ).shouldHandleError(
|
||||||
error,
|
error,
|
||||||
stackFrames
|
stackFrames
|
||||||
);
|
);
|
||||||
|
@ -203,7 +256,7 @@ describe( 'RemoteLogger', () => {
|
||||||
|
|
||||||
it( 'should return false for WooCommerce errors with no stack frames', () => {
|
it( 'should return false for WooCommerce errors with no stack frames', () => {
|
||||||
const error = new Error( 'Test error' );
|
const error = new Error( 'Test error' );
|
||||||
const result = ( logger as any ).shouldSendError( error, [] );
|
const result = ( logger as any ).shouldHandleError( error, [] );
|
||||||
expect( result ).toBe( false );
|
expect( result ).toBe( false );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -214,7 +267,7 @@ describe( 'RemoteLogger', () => {
|
||||||
() => true
|
() => true
|
||||||
);
|
);
|
||||||
const error = new Error( 'Test error' );
|
const error = new Error( 'Test error' );
|
||||||
const result = ( logger as any ).shouldSendError( error, [] );
|
const result = ( logger as any ).shouldHandleError( error, [] );
|
||||||
expect( result ).toBe( true );
|
expect( result ).toBe( true );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
@ -255,18 +308,37 @@ describe( 'RemoteLogger', () => {
|
||||||
describe( 'init', () => {
|
describe( 'init', () => {
|
||||||
beforeEach( () => {
|
beforeEach( () => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
window.wcTracks = { isEnabled: true };
|
|
||||||
|
( getSetting as jest.Mock ).mockImplementation(
|
||||||
|
( key, defaultValue ) => {
|
||||||
|
if ( key === 'isRemoteLoggingEnabled' ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
);
|
||||||
} );
|
} );
|
||||||
|
|
||||||
it( 'should not initialize the logger if Tracks is not enabled', () => {
|
it( 'should not initialize or log when remote logging is disabled', () => {
|
||||||
window.wcTracks = { isEnabled: false };
|
// Mock the getSetting function to return false for isRemoteLoggingEnabled
|
||||||
|
( getSetting as jest.Mock ).mockImplementation(
|
||||||
|
( key, defaultValue ) => {
|
||||||
|
if ( key === 'isRemoteLoggingEnabled' ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
init( { errorRateLimitMs: 1000 } );
|
init( { errorRateLimitMs: 1000 } );
|
||||||
expect( () => log( 'info', 'Test message' ) ).not.toThrow();
|
log( 'info', 'Test message' );
|
||||||
|
expect( fetchMock ).not.toHaveBeenCalled();
|
||||||
} );
|
} );
|
||||||
|
|
||||||
it( 'should initialize the logger if Tracks is enabled', () => {
|
it( 'should initialize and log without throwing when remote logging is enabled', () => {
|
||||||
init( { errorRateLimitMs: 1000 } );
|
init( { errorRateLimitMs: 1000 } );
|
||||||
expect( () => log( 'info', 'Test message' ) ).not.toThrow();
|
expect( () => log( 'info', 'Test message' ) ).not.toThrow();
|
||||||
|
expect( fetchMock ).toHaveBeenCalled();
|
||||||
} );
|
} );
|
||||||
|
|
||||||
it( 'should not initialize the logger twice', () => {
|
it( 'should not initialize the logger twice', () => {
|
||||||
|
@ -280,9 +352,33 @@ describe( 'init', () => {
|
||||||
} );
|
} );
|
||||||
|
|
||||||
describe( 'log', () => {
|
describe( 'log', () => {
|
||||||
it( 'should not log if Tracks is not enabled', () => {
|
it( 'should not log if remote logging is disabled', () => {
|
||||||
window.wcTracks = { isEnabled: false };
|
( getSetting as jest.Mock ).mockImplementation(
|
||||||
|
( key, defaultValue ) => {
|
||||||
|
if ( key === 'isRemoteLoggingEnabled' ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
log( 'info', 'Test message' );
|
log( 'info', 'Test message' );
|
||||||
expect( fetchMock ).not.toHaveBeenCalled();
|
expect( fetchMock ).not.toHaveBeenCalled();
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
describe( 'captureException', () => {
|
||||||
|
it( 'should not log error if remote logging is disabled', () => {
|
||||||
|
( getSetting as jest.Mock ).mockImplementation(
|
||||||
|
( key, defaultValue ) => {
|
||||||
|
if ( key === 'isRemoteLoggingEnabled' ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
captureException( new Error( 'Test error' ) );
|
||||||
|
expect( fetchMock ).not.toHaveBeenCalled();
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
|
@ -39,3 +39,5 @@ export function mergeLogData( target: LogData, source: Partial< LogData > ) {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isDevelopmentEnvironment = process.env.NODE_ENV === 'development';
|
||||||
|
|
|
@ -43,6 +43,7 @@ import CurrencyFactory from '@woocommerce/currency';
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { findComponentMeta } from '~/utils/xstate/find-component';
|
import { findComponentMeta } from '~/utils/xstate/find-component';
|
||||||
|
import { initRemoteLogging } from '~/lib/init-remote-logging';
|
||||||
import { IntroOptIn } from './pages/IntroOptIn';
|
import { IntroOptIn } from './pages/IntroOptIn';
|
||||||
import {
|
import {
|
||||||
UserProfile,
|
UserProfile,
|
||||||
|
@ -330,6 +331,7 @@ const updateTrackingOption = fromPromise(
|
||||||
) {
|
) {
|
||||||
window.wcTracks.enable( () => {
|
window.wcTracks.enable( () => {
|
||||||
initializeExPlat();
|
initializeExPlat();
|
||||||
|
initRemoteLogging();
|
||||||
resolve(); // resolve the promise only after explat is enabled by the callback
|
resolve(); // resolve the promise only after explat is enabled by the callback
|
||||||
} );
|
} );
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import { Component, ReactNode, ErrorInfo } from 'react';
|
import { Component, ReactNode, ErrorInfo } from 'react';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { Button } from '@wordpress/components';
|
import { Button } from '@wordpress/components';
|
||||||
|
import { captureException } from '@woocommerce/remote-logging';
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
|
@ -34,9 +35,22 @@ export class ErrorBoundary extends Component<
|
||||||
return { hasError: true, error };
|
return { hasError: true, error };
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch( _error: Error, errorInfo: ErrorInfo ) {
|
componentDidCatch( error: Error, errorInfo: ErrorInfo ) {
|
||||||
this.setState( { errorInfo } );
|
this.setState( { errorInfo } );
|
||||||
// TODO: Log error to error tracking service
|
|
||||||
|
// Limit the component stack to 10 calls so we don't send too much data.
|
||||||
|
const componentStack = errorInfo.componentStack
|
||||||
|
.trim()
|
||||||
|
.split( '\n' )
|
||||||
|
.slice( 0, 10 )
|
||||||
|
.map( ( line ) => line.trim() );
|
||||||
|
|
||||||
|
captureException( error, {
|
||||||
|
severity: 'critical',
|
||||||
|
extra: {
|
||||||
|
componentStack,
|
||||||
|
},
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRefresh = () => {
|
handleRefresh = () => {
|
||||||
|
|
|
@ -8,6 +8,12 @@ import {
|
||||||
withCurrentUserHydration,
|
withCurrentUserHydration,
|
||||||
withSettingsHydration,
|
withSettingsHydration,
|
||||||
} from '@woocommerce/data';
|
} from '@woocommerce/data';
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { initRemoteLogging } from './lib/init-remote-logging';
|
||||||
|
// Initialize remote logging early to log any errors that occur during initialization.
|
||||||
|
initRemoteLogging();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { init } from '@woocommerce/remote-logging';
|
||||||
|
|
||||||
|
export const initRemoteLogging = () => {
|
||||||
|
init( {
|
||||||
|
errorRateLimitMs: 60000, // 1 minute
|
||||||
|
} );
|
||||||
|
};
|
|
@ -163,6 +163,7 @@
|
||||||
"@woocommerce/number": "workspace:*",
|
"@woocommerce/number": "workspace:*",
|
||||||
"@woocommerce/onboarding": "workspace:*",
|
"@woocommerce/onboarding": "workspace:*",
|
||||||
"@woocommerce/product-editor": "workspace:*",
|
"@woocommerce/product-editor": "workspace:*",
|
||||||
|
"@woocommerce/remote-logging": "workspace:*",
|
||||||
"@woocommerce/tracks": "workspace:*",
|
"@woocommerce/tracks": "workspace:*",
|
||||||
"@wordpress/babel-preset-default": "^6.17.0",
|
"@wordpress/babel-preset-default": "^6.17.0",
|
||||||
"@wordpress/block-editor": "^9.8.0",
|
"@wordpress/block-editor": "^9.8.0",
|
||||||
|
@ -305,6 +306,9 @@
|
||||||
"node_modules/@woocommerce/tracks/build",
|
"node_modules/@woocommerce/tracks/build",
|
||||||
"node_modules/@woocommerce/tracks/build-module",
|
"node_modules/@woocommerce/tracks/build-module",
|
||||||
"node_modules/@woocommerce/tracks/build-types",
|
"node_modules/@woocommerce/tracks/build-types",
|
||||||
|
"node_modules/@woocommerce/remote-logging/build",
|
||||||
|
"node_modules/@woocommerce/remote-logging/build-module",
|
||||||
|
"node_modules/@woocommerce/remote-logging/build-types",
|
||||||
"node_modules/@woocommerce/product-editor/build",
|
"node_modules/@woocommerce/product-editor/build",
|
||||||
"node_modules/@woocommerce/product-editor/build-module",
|
"node_modules/@woocommerce/product-editor/build-module",
|
||||||
"node_modules/@woocommerce/product-editor/build-style",
|
"node_modules/@woocommerce/product-editor/build-style",
|
||||||
|
|
|
@ -42,6 +42,7 @@ const wcAdminPackages = [
|
||||||
'onboarding',
|
'onboarding',
|
||||||
'block-templates',
|
'block-templates',
|
||||||
'product-editor',
|
'product-editor',
|
||||||
|
'remote-logging',
|
||||||
];
|
];
|
||||||
// wpAdminScripts are loaded on wp-admin pages outside the context of WooCommerce Admin
|
// wpAdminScripts are loaded on wp-admin pages outside the context of WooCommerce Admin
|
||||||
// See ./client/wp-admin-scripts/README.md for more details
|
// See ./client/wp-admin-scripts/README.md for more details
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Integrate JS remote logging package in WooCommerce Admin
|
|
@ -3,6 +3,7 @@ namespace Automattic\WooCommerce\Blocks\Assets;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Blocks\Package;
|
use Automattic\WooCommerce\Blocks\Package;
|
||||||
use Automattic\WooCommerce\Blocks\Domain\Services\Hydration;
|
use Automattic\WooCommerce\Blocks\Domain\Services\Hydration;
|
||||||
|
use Automattic\WooCommerce\Internal\Logging\RemoteLogger;
|
||||||
use Exception;
|
use Exception;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
@ -89,6 +90,7 @@ class AssetDataRegistry {
|
||||||
'dateFormat' => wc_date_format(),
|
'dateFormat' => wc_date_format(),
|
||||||
'homeUrl' => esc_url( home_url( '/' ) ),
|
'homeUrl' => esc_url( home_url( '/' ) ),
|
||||||
'locale' => $this->get_locale_data(),
|
'locale' => $this->get_locale_data(),
|
||||||
|
'isRemoteLoggingEnabled' => wc_get_container()->get( RemoteLogger::class )->is_remote_logging_allowed(),
|
||||||
'dashboardUrl' => wc_get_account_endpoint_url( 'dashboard' ),
|
'dashboardUrl' => wc_get_account_endpoint_url( 'dashboard' ),
|
||||||
'orderStatuses' => $this->get_order_statuses(),
|
'orderStatuses' => $this->get_order_statuses(),
|
||||||
'placeholderImgSrc' => wc_placeholder_img_src(),
|
'placeholderImgSrc' => wc_placeholder_img_src(),
|
||||||
|
|
|
@ -279,6 +279,7 @@ class WCAdminAssets {
|
||||||
'wc-navigation',
|
'wc-navigation',
|
||||||
'wc-block-templates',
|
'wc-block-templates',
|
||||||
'wc-product-editor',
|
'wc-product-editor',
|
||||||
|
'wc-remote-logging',
|
||||||
);
|
);
|
||||||
|
|
||||||
$scripts_map = array(
|
$scripts_map = array(
|
||||||
|
|
|
@ -3600,6 +3600,9 @@ importers:
|
||||||
'@woocommerce/product-editor':
|
'@woocommerce/product-editor':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/js/product-editor
|
version: link:../../packages/js/product-editor
|
||||||
|
'@woocommerce/remote-logging':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/js/remote-logging
|
||||||
'@woocommerce/tracks':
|
'@woocommerce/tracks':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/js/tracks
|
version: link:../../packages/js/tracks
|
||||||
|
|
Loading…
Reference in New Issue