In my previous blogpost, I have explained about JavaScript errors and how we can handle them in general and how we can handle them at application level. If you have not read it yet, you can read it here. In this blogpost, I will explain an approach to log JavaScript errors to a database table.
- event.message: JavaScript error message
- event.filename: If source of the error is from a JavaScript file, then this will give full path of JavaScript file. If error is from inline JavaScript code written in APEX page, then this will give full APEX page URL including APEX session.
- event.lineno: Line number where error has occurred. Depending on event.filename, it could refer to line number from JavaScript file or from generated HTML page.
- event.colno: Column number where error has occurred. It should be read along with event.lineno.
- event.error.stack: Call stack information. Similar to DBMS_UTILITY.FORMAT_ERROR_STACK in PL/SQL.
Now, let's create a table to store these JavaScript errors. Please find sample table creation script below which I have used for the demo.
-- create tables create table apex_js_error_log ( aje_log_id number generated by default on null as identity constraint apex_js_error_log_id_pk primary key, message varchar2(4000 char), filename varchar2(255 char), lineno number, colno number, error_stack clob, app_id number, page_id number, session_id number, app_user varchar2(255 char), created_on date not null, created_by varchar2(255 char) not null ); -- triggers create or replace trigger apex_js_error_log_bi before insert on apex_js_error_log for each row begin :new.created_on := sysdate; :new.created_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user); end apex_js_error_log_bi; /
Let's create a PL/SQL function to log errors.
CREATE OR REPLACE FUNCTION log_js_error ( p_message IN VARCHAR2, p_filename IN VARCHAR2, p_lineno IN NUMBER, p_colno IN NUMBER, p_error_stack IN VARCHAR2, p_session_id IN NUMBER, p_app_id IN NUMBER DEFAULT apex_application.g_flow_id, p_page_id IN NUMBER DEFAULT apex_application.g_flow_step_id, p_app_user IN VARCHAR2 DEFAULT apex_application.g_user ) RETURN NUMBER IS l_aje_log_id apex_js_error_log.aje_log_id%TYPE; BEGIN INSERT INTO apex_js_error_log ( message, filename, lineno, colno, error_stack, app_id, page_id, session_id, app_user ) VALUES ( p_message, p_filename, p_lineno, p_colno, p_error_stack, p_app_id, p_page_id, p_session_id, p_app_user ) RETURNING aje_log_id INTO l_aje_log_id; RETURN l_aje_log_id; END log_js_error;
Let's create an application process LOG_JS_ERROR to call and log errors.
- Name: LOG_JS_ERROR
- Process Point: Ajax Callback: Run this application process when requested by a page process.
- Source:
- Code: As shown below
DECLARE l_aje_log_id apex_js_error_log.aje_log_id%TYPE; l_json_output json_object_t; BEGIN l_aje_log_id := log_js_error(p_message => apex_application.g_x01, p_filename => apex_application.g_x02, p_lineno => apex_application.g_x03, p_colno => apex_application.g_x04, p_error_stack => apex_application.g_x05, p_session_id => :app_session); -- Send error log id as JSON object to JavaScript -- If you are using older version of DB (< 12.2), then you can use APEX_JSON APIs to generate JSON l_json_output := NEW json_object_t; l_json_output.put('aje_log_id', l_aje_log_id); htp.p(l_json_output.to_string); END;
And finally, let's create a JavaScript function to make an AJAX call which logs errors into DB table.
function logError(pMessage, pFileName, pLineNo, pColNo, pErrorStack) { // clear the errors apex.message.clearErrors(); // make an AJAX call and log error // error stack could be more than 32k, limit text to 30k var result = apex.server.process("LOG_JS_ERROR", { x01: pMessage, x02: pFileName, x03: pLineNo, x04: pColNo, x05: pErrorStack.substr(0, 30000) }); result.done(function (data) { apex.message.showPageSuccess("Thanks for reporting this error. Reference # " + data.aje_log_id); }).fail(function (jqXHR, textStatus, errorThrown) { // APEX already shows errorThrown as error message // Somecases jqXHR.responseText will have useful info, so show jqXHR.responseText also as error message apex.message.showErrors({ type: "error", location: "page", message: jqXHR.responseText, unsafe: false }); }); }
In the demo application, I have added this function to js_error_logging.js file and then I have referred the JS file in Application > User Interfaces > JavaScript > File URLs section. If you are not sure what's best way to include JavaScript code to APEX applications, then please do read Adding JavaScript to an Application Express application from APEX documentation.
Now, let's create Dynamic Action (DA) in Page-0 as below.
- Name: Handle Errors or any proper name
- Execution Options:
- Sequence: 0
- When:
- Event: Page Load
- True Action:
- Action: Execute JavaScript Code
- Code: As shown below
window.addEventListener('error', function (event) { // show APEX error message // provide option for users to log/report the error apex.message.showErrors({ type: "error", location: "page", message: "Unhandled JavaScript Error. Please <a id='log_js_error' style='cursor: pointer;'>click here</a> to report this error.", unsafe: false }); // log error if user clicks on the link $("#log_js_error").click(function () { logError(event.message, event.filename, event.lineno, event.colno, event.error.stack); }); });
let timerId = setTimeout(function doSomeTask() { // throws error console.log(pValue); // go for next iteration only if current iteration is successful // below code will be executed only if all of the above code is executed successfully timerId = setTimeout(doSomeTask, 5000); }, 5000);
Comments