Application Development


Chapter 37. Processing Application Errors

While users of your application need to be notified when errors occur, it is also important to know what is happening on your servers, both during development and at runtime. Errors that occur remotely may have no meaning to a user, but in most cases, you want to determine which errors should be posted or broadcast from server to client, and what information should be displayed that will be most useful to a user of your application.

Panther provides global variables and hook functions to help you manage errors that result from requests made to a database. Default error handlers are installed to log errors on your server from Panther's database drivers and from the database engine. In addition, you can write (in JPL or C) and install customized error handlers. With a single JPL procedure, you can change the way errors are handled and control which messages are logged.

This chapter discusses:


Default Error Handlers

The behavior of the default error handlers depends on where the database interaction occurs, that is, on which agent (client or server) and whether, particularly for servers, the application is in development or production.

Figure 37-1 By default, database errors are logged on the server. The application client outputs database errors only if database calls were made directly from client to database.

When database requests are submitted to the database via a service request from an application server, error processing takes place on the server. Therefore, there is no direct output to the client. To ensure that your application reports these errors, error handlers are installed on the server at initialization. The handlers let you monitor the results of DBMS events on the server by logging server activity to a central event log. Depending on the outcome, you might also want to display pertinent information on the client by including status/error information with service call results (refer to page 26-1 for information on displaying messages to a user).

If calls are made directly to a database from the client, the Panther default hook function provides output directly to the user.

Server Activity

Panther installs different default handlers for the development environment and for your production application. The handlers log messages to the central event log. One handler logs each DBMS command, the other logs errors if they occur as the result of DBMS commands.

The default development handler for the DBMS ONENTRY function is sm_tp_dbms_cmd_log, which logs each DBMS command. To minimize overhead and since no logging of commands is required at runtime, no handler is installed for production applications.

The default development and production error handler for the DBMS ONERROR function is sm_tp_dbms_error_print_all which logs all DBMS command errors to the central event log.

You can override the default handlers by installing your own function to handle errors. The application can also install an exit function to process all error and status information and to display this information in the application as well as log it to the central event log. Writing and installing your own handler is covered later in this chapter.

Client Output

Panther installs a default error handler which is used when executing DBMS commands directly from the client to a database, that is, without access to the request broker. In general, direct access to the database is not recommended in three-tier architecture since your application cannot take advantage of three-tier features, such as routing, during basic client/server processing.

If an error occurs, the default error handler displays the following information in a dialog box:

For this type of error, the default error handler returns a -1 to its caller. If an error occurs while executing JPL, Panther aborts the JPL procedure where the error occurred. An aborted JPL procedure always returns -1 to its caller.

An application can override the default handler by installing its own function to handle errors. It can also install an exit function to process all error and status information and to display this information in the application.


Variables for Logging Error and Status Information

Panther supplies several predefined variables where it stores database error and status data for the application. These global variables (begin with the characters @dm) are automatically defined at initialization and maintained by Panther.

Before executing a DBMS statement, Panther clears the contents of all DBMS global variables. After executing a DBMS statement, Panther updates these variables with any error, warning or status information returned by the engine.

In addition to engine-specific codes and messages, Panther also supplies engine-independent codes and messages. These messages are defined in dmerror.h.

The variables and their values are available in JPL and in C via Panther library functions like sm_n_getfield and sm_n_fptr.

Table 37-1 Database global variables

Variable Description

@dmretcode

Status of the last executed DBMS statement; value is 0 or one of the codes defined in dmerror.h.

@dmretmsg

Engine-independent message associated with last executed DBMS statement; value is either empty or one of the messages (defined in Panther's message file. If @dmretcode is 0, this variable is empty.

@dmengerrcode

Engine-specific error code for last executed DBMS statement; value is 0 (engine did not detect any errors) or an engine-specific code.

@dmengerrmsg

Engine-specific error message for last executed DBMS statement. If @dmengerrcode is 0, this variable is empty.

@dmengwarncode

Engine-specific warning code or bit setting for last executed DBMS statement. If empty, the engine did not detect any warning conditions.

@dmengwarnmsg

Engine-specific warning message for last executed DBMS statement. If @dmengwarncode is a byte or is blank, this variable is empty.

@dmengreturn

Return code from last executed stored procedure; value is either blank (engine did not supply a return code) or an integer.

@dmrowcount

Number of rows fetched to Panther variables by last SELECT or CONTINUE statement. It can also contain the number of rows affected by INSERT, UPDATE or DELETE statements.

@dmserial

Engine-generated value for a serial column; value is 0 or an appropriate serial value for the column.

For more information on these variables, refer to page 12-1 in the Programming Guide.


Database Error Event Functions

Panther provides the following database-specific error event functions which are invoked when specific database events occur:

ONENTRY

Called before executing any DBMS command from JPL or C.

ONEXIT

Called after executing any DBMS command from JPL or C.

ONERROR

Called if an error occurs while executing any DBMS command from JPL or C.

The error event functions receive three arguments:

ONENTRY Function
Before executing a DBMS command from JPL or C, Panther executes the application's installed ONENTRY function. An ONENTRY function is useful for logging or debugging statements. The default handler for development servers is sm_tp_dbms_cmd_log which logs each command to the central event log.

If you are supplying your own ONENTRY function, you can also use it to modify the Panther environment, for instance, to remap cursor control keys or change protection-type properties on widgets on client screens with a direct connection to a database.

ONEXIT Function
After executing a DBMS command from JPL or C, Panther executes the application's installed ONEXIT function. An ONEXIT function is useful for logging or debugging statements. This function is also useful for checking error and status codes after each command.

If you are supplying a custom ONEXIT function, you can use it to modify the Panther environment, for instance, to remap cursor control keys or change protection-type properties on widgets on client screens with a direct connection to a database.

ONERROR Function
If an error occurs in the database driver while executing a DBMS command from JPL or C, Panther executes the application's installed ONERROR function. All errors are logged to the event log with the default handler sm_tp_dbms_error_print_all. An ONERROR function can log the values of the global DBMS error variables. It might also log the text of the command that failed.

If the error occurred while executing a command from JPL, the ONERROR function determines whether control is returned to the procedure or to the procedure's caller.

If you are using JPL, it is recommended that you install an ONERROR function. This ensures consistent error handling throughout the application and reduces the amount of code needed to handle errors. If an ONEXIT function is also installed, Panther calls the ONEXIT function before the ONERROR function.

Writing an Error Event Function

You can write database error event functions in JPL or C.

A JPL error event function is installed as follows:

DBMS { ONENTRY | ONEXIT | ONERROR } JPL entryPoint

where entryPoint is an entry point to a JPL module. The entry point can be a procedure name, a file name, or the name of your custom error handler. Refer to the JPL section of the Programming Guide for more information.

A C error event function is installed as follows:

DBMS { ONENTRY | ONEXIT | ONERROR } CALL function

where function is a prototyped function that takes three arguments: two strings and an integer. For example, the following entry in the pfuncs structure installs myfunc as a prototyped function that returns an integer:

static struct fnc_data pfuncs[] =
{
...
SM_INTFNC("myfunc(s,s,i)", myfunc),
...
};

For more information on installing prototyped functions, refer to page 44-9.

To turn off a custom error event function and reinstate the default handler, execute the command with no arguments. For example:

DBMS ONERROR

For more information and examples of each event function, refer to page 11-42 in the Programming Guide.

ONENTRY
The return code from an ONENTRY function is ignored if the current command was executed from JPL. If the command was executed from C, the return code is returned to the calling function. It is recommended that this function return 0.

ONEXIT
The return code from an ONEXIT function is ignored unless an error occurred while executing a DBMS command using JPL. If the return code from the function is non-zero, Panther aborts the JPL procedure where the error occurred and returns -1 to the caller. If the command is executed from C, the return code is returned to the calling function.

If the application is also using an ONERROR function, the return code from the ONERROR function overrides the return code from the ONEXIT function.

ONERROR
If an application is using an installed error handler, the error handler determines the handling for errors that occur while using JPL.

If an error occurs in Panther's database interface while executing JPL, a non-zero return code aborts the JPL procedure where the error occurred. The procedure's caller (either Panther or another JPL procedure) gains control. If the return code is 0, the JPL procedure resumes control; Panther executes the next statement in the JPL procedure.

If an error occurs in Panther's database interface while executing a C function, the ONERROR return code is returned to the calling function.

The return code from an ONERROR function overrides the return code from an ONEXIT function.


Custom Error Handlers

It is recommended that your Panther application use an error handler. If the default handlers do not accomplish what you desire, you can write a custom error handler in JPL or C. In this way you can customize the error messages appearing in your application. You can use any of the global variables as part of an error handler. For example, it can use @dmretmsg to display a message from Panther's database driver or @dmengerrmsg to display an engine-specific error message. It might also display its own messages depending on the values in @dmretcode and @dmengerrcode.

The procedure's return code determines whether or not JPL continues or aborts the procedure it was executing.

There are two classes of errors in Panther's database drivers:

There are also Panther and JPL errors which are not a part of Panther's database driver. A JPL procedure might fail because of JPL syntax or colon preprocessing errors. If a JPL error occurs, Panther outputs an error message describing the error, the source of the JPL statement, and the statement that failed. Furthermore, it aborts the JPL procedure where such an error occurred and returns control to the procedure's caller. It is assumed that JPL and Panther errors are detected and corrected during application development. The only time that an application might need special handling for these errors is during transaction processing. For more information, refer to page 28-10.

An ONERROR function overrides Panther's default DBMS error handler. The function controls the display of error messages. If the error occurred while executing a command from JPL, the ONERROR function also determines whether control is returned to the procedure or to the procedure's caller.

If you using JPL, install an ONERROR function. This ensures consistent error handling throughout the application and reduces the amount of code needed to handle errors. If an ONEXIT function is also installed, Panther calls the ONEXIT function, then the ONERROR function.

Example

This procedure checks if the error is DM_ALREADY_ON. In this case, it logs a message and returns 0. For all other errors, it checks for an engine error code. If there is an engine error, it logs the statement and engine-specific error message.

proc screen_entry
DBMS ONERROR jpl dbi_error_handler
...
return

proc dbi_error_handler (statement, engine)

if (@dmretcode == DM_ALREADY_ON)
{
msg emsg "You are already logged on."
return 0
}

if (@dmengerrcode != 0)
{
msg emsg @dmretmsg "%N" "Statement :statement" "%N" \
":engine Error :@dmengerrcode :@dmengerrmsg"
}
else
{
msg emsg "Application Error: :@dmretmsg " \
"See the DBA for assistance."
}
return 1

The following example illustrates how a database engine error is logged to the central event log.

proc dbi_error_handler(statement, engine)
{
vars message1, message2, message3

message1 = "DBMS ERROR " ## statement
message2 = "Database code " ## @dmretcode ## \
", Database message " ## @dmretmsg
message3 = "DBMS code " ## @dmengerrcode ## \
", DBMS message " ## @dmengerrmsg

log message1
log message2
log message3

// On DBMS failure, terminate service request with failure
service_return failure ()
}