Using a pl/sql procedure to log errors and handle exceptions - oracle

so far stack overflow and the oracle forums and docs have been my best friend in learning PLSQL. I'm running into an issue here. Any advice is appreciated. I'm writing a procedure that would be used to log any errors a package may encounter and log them into the error log table I created. here is my code thus far.
CREATE OR REPLACE PROCEDURE APMS.test_procedure AS
procedure write_error_log (errcode number, errstr varchar2, errline varchar2) is
pragma autonomous_transaction;
-- this procedure stays in its own new private transaction
begin
INSERT INTO error_log
(ora_err_tmsp,
ora_err_number,
ora_err_msg,
ora_err_line_no)
values (CURRENT_TIMESTAMP,
errcode,
errstr,
errline);
COMMIT; -- this commit does not interfere with the caller's transaction.
end write_error_log;
BEGIN
INSERT INTO mockdata
VALUES ('data1', 'mockname', 'mockcity');
exception when others then
write_error_log(sqlcode,sqlerrm,dbms_utility.format_error_backtrace);
raise;
END test_procedure;
/
In the procedure I currently am using a mockdata table to induce an invalid number error and log that to the error_log table. At this point the error log table proves to be functional and inserts the data needed. The next step for me is to use this procedure to be used in the exception handlers in other programs so that the error is caught and logged to the table. Currently, my procedure is only unique to the mock_data table. My mentor/superior is telling me I need to pass this program some parameters to use it in other packages and exception handlers. I'm just having a bit of trouble. Any help would be appreciated thank you!

Steven Feuerstein has written several articles in Oracle Magazine on how to handle errors in PLSQL. He offers a small framework (errpkg) for doing this. The DRY principle (Don't Repeat Yourself) is his mantra!
https://resources.oreilly.com/examples/0636920024859/blob/master/errpkg.pkg

First, you should not make your caller pass in errline. That's very tedious! And what happens when the developer needs to insert a line or two of code? Do they need to update every call to write_error_log after that point to update the line numbers? Your write_error_log should use dbms_utility.format_call_stack (or the 12c more convenient variant of that.. don't have its name handy) to figure out what line of code issued the call to write_error_log.
Then, you should have a 2nd procedure called, say, write_exception. All that needs to do is something like this:
write_error_log (SQLCODE, SUBSTR (DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace, 1, 4000));

Related

Re-run PL/SQL script command

I have a simple insert script that I want to expand upon.
DECLARE
i varchar2(3000) := dbms_random.string('A',8);
BEGIN
INSERT INTO BUYERS
(USER_ID,BUYER_CD,BUYER_ENG_NM,REG_DT)
VALUES
(i,'tes','test','test');
EXCEPTION WHEN OTHER
THEN
(this is where I need help)
end;
We have dynamic replication going on between two DB's. However, for some odd reason we have to run a script twice for the changes to commit to both DB's for that reason I am creating a script that will attempt to do a insert amongst all tables. As of now I'm only working on one table. Within the exception handler how do I make the script run again when the initial insert fails? Any help is appreciated.
If a problem happens with the insert then the best approach is to find out what the error is and raise the error. This is best accomplished by an autonomous logging procedure that will record the what, where, when and then RAISE the error again so processing stops. You do not want to take a chance of inserting records once, twice or not at all which could happen if the errors are not raised again.
The LOG_ERROR procedure below can be created from the answers to your previous questions about error handling.
DECLARE
i varchar2(3000) := dbms_random.string('A',8);
BEGIN
INSERT INTO BUYERS
(USER_ID,BUYER_CD,BUYER_ENG_NM,REG_DT)
VALUES
(i,'tes','test','test');
EXCEPTION WHEN OTHER
THEN
--by the time you got here there is no point in trying to insert again
LOG_ERROR(SQLERRM, LOCATION);
RAISE;
end;

Handling and logging ORA-06512 error code

I have just created an error_log table to log any errors that a procedure/package may run into. the error log table is as follows
CREATE TABLE APMS.ERROR_LOG
(
ORA_ERR_TMSP TIMESTAMP(6) NOT NULL,
ORA_ERR_NUMBER NUMBER(5) NOT NULL,
ORA_ERR_MSG VARCHAR2(200 CHAR) NOT NULL,
ORA_ERR_TXT VARCHAR2(500 CHAR) NOT NULL,
ORA_ERROR_OPTYP CHAR(1 CHAR) NOT NULL,
PROGRAM_NAME VARCHAR2(50 CHAR) NOT NULL,
ORA_IN_OUT VARCHAR2(500 CHAR) NOT NULL
)
I created a mock table an purposely induced the ORA-06512 error by inserting a character string in a timestamp field using a procedure. here is the procedure which inserts dummy data into my mock table with the purpose of inducing an error and logging it into my error_log table.
create or replace procedure test_procedure as
begin
insert into mockdata values ('data1','mockname','mockcity');
commit;
exception
when others then
insert into error_log
values
(ora_err_tmsp,ora_err_number,ora_err_msg,ora_err_txt,ora_err_optyp,program_name,ora_in_out);
values
(current_timestamp,sqlcode,'sqlerrm', 'detailed information','i','test_procedure','i');
commit;
end;
/
when I attempt to run/compile it I get the following error.
[Error] PLS-00103 (9: 1): PLS-00103: Encountered the symbol "VALUES" when expecting one of the following:
( begin case declare end exit for goto if loop mod null
pragma raise return select update when while with
<an
I am a complete beginner at pl/sql so any help is appreciated.
Before getting into your code: if you are planning to write that kind of logging in every procedure you write, maybe you should simply avoid doing it and keep at reach of your hand this trigger and enable it just when you need it: this trigger might be handy in a testing environment or if you are in deep trouble and you need to collect all possible error that happen in a given time:
CREATE OR REPLACE TRIGGER log_all_errors AFTER SERVERERROR ON DATABASE
declare
procedure log_error is
pragma autonomous_transaction;
begin
INSERT INTO error_log
VALUES (SYSDATE, SYS.LOGIN_USER, SYS.INSTANCE_NUM, SYS.DATABASE_NAME, DBMS_UTILITY.FORMAT_ERROR_STACK);
commit;
end;
BEGIN
log_error;
END log_all_errors ;
this trigger will log ALL errors that will happen in your system. it is not a good idea to keep it enabled permanently: you can use it for doing some troubleshooting in emergency and you might want adjust its code to log only some kind of errors, but it is a starting point.
if you do some research you will discover that you can even log the SQL text of the statement that caused the error.
keep in mind that there are some errors that are not catched by this trigger (i am referring to NO_DATA_FOUND and TOO_MANY_ROWS errors): there is simply too much code that normally uses these exceptions during its normal lifetime, so the guys at oracle decided not to trap these errors.
Now let's get back to your code: your approach, as other have pointed out, is not neutral to the normal execution of the program:
you are logging the error, right, but you are hiding the error to the program calling your procedure. It is not a good idea: the calling program surely would like to know that something wrong has happened and it would like to issue a "rollback" in order to cancel all previous work, instead of continuing like nothing wrong did happen.
Not only you are hiding the error, but you are also issuing a commit whenever an error happens: this is most likely the opposite of what your calling program would do in case of an error: surely the caller would issue a rollback. Instead you are making permanent all the partial work done until the error happened.
as a side note: the above problem of the unwanted commit even when no errors happen within your procedure: you are committing also when no errors are raised. this means that if the calling program enconuters some problems after having called your procedure, it won't be able to rollback anything it did before calling your code.
It generally is a bad practice to commit or rollback inside a procedure, unless you are more than sure that such procedure will never be called as part of a bigger transaction (for example: it is ok to commit if your procedure is the main body of a database job)
So: how can you write an error log without interfering with the calling program? you do it by writing the log in a dedicted autonomous transaction" procedure: whatever you do in procedure marked as "authonomous transaction" runs in its own separate transaction: you commit or rollback only what happens inside it.
(As you can see, I used an autonomous transaction also in the above trigger)
your code would be more "kind", to the calling program, if written this way:
CREATE OR REPLACE PROCEDURE test_procedure AS
procedure write_error_log (errcode number, errstr varchar2) is
pragma autonomous_transaction;
-- whatever we do in this procedure stays in its own new private transaction
begin
INSERT INTO error_log
(ora_err_tmsp,
ora_err_number,
ora_err_msg,
ora_err_txt,
ora_err_optyp,
program_name,
ora_in_out)
values (CURRENT_TIMESTAMP,
errcode,
errstr,
'detailed information',
'i',
'test_procedure',
'i');
COMMIT; -- this commit does not interfere with the caller's transaction.
end write_error_log;
BEGIN
INSERT INTO mockdata
VALUES ('data1', 'mockname', 'mockcity');
--here you were committing: you'd better not do it:
-- you are making impossible for the calling program to roll back
-- COMMIT;
exception when others then
write_error_log(sqlcode,sqlerrm);
raise; -- you should NOT hide the exception to the caller program, so you'd better re-raise it!
END test_procedure;
Try this:
CREATE OR REPLACE PROCEDURE test_procedure
AS
BEGIN
INSERT INTO mockdata
VALUES ('data1', 'mockname', 'mockcity');
COMMIT;
EXCEPTION
WHEN OTHERS
THEN
INSERT INTO error_log
(ora_err_tmsp,
ora_err_number,
ora_err_msg,
ora_err_txt,
ora_err_optyp,
program_name,
ora_in_out)
values (CURRENT_TIMESTAMP,
SQLCODE,
sqlerrm,
'detailed information',
'i',
'test_procedure',
'i');
COMMIT;
END;
/

How do I pass a cursor value when calling a stored procedure?

This is only my second time diagnosing a PL/SQL procedure. I need to test the code in the stored procedure, and I'm trying to call it in SQL Developer. I have run the error details report, and the code has no obvious bugs in it.
So now I am trying to run it through a test window so I can see if the output is correct. However I can't seem to get the right argument for the 3 parameter. Here are the parameters in the the procedure.
CREATE OR REPLACE PROCEDURE ADVANCE.WW_DEATHDATE_REPORT(begindate varchar2, enddatevarchar2, RC1 IN OUT du_refCUR_PKG.RC) AS
Here is the Code I am trying to use to call the procedure. What do I need to do to get it to run correct? I keep getting error messages saying I'm using a wrong value in the parameter.
BEGIN
ADVANCE.WW_DEATHDATE_REPORT('20100101','20150101',du_refcur_pkg);
END;
There are multiple ways to do this, one way is like the below,
DECLARE
du_refcur_pkg SYS_REFCURSOR;
BEGIN
OPEN du_refcur_pkg FOR SELECT ... ;
ADVANCE.WW_DEATHDATE_REPORT('20100101','20150101',du_refcur_pkg);
END;
Another way would be,
BEGIN
ADVANCE.WW_DEATHDATE_REPORT( '20100101','20150101', CURSOR (SELECT ... ) );
END;

oracle plsql: retrieve runtime parameter values when you call a procedure

I need a generalized method to get list of runtime parameters (values) when I call a procedure. I need something similar to the $$PLSQL_UNIT that returns the name of the running procedure.
(plsql Oracle 10g)
E.g. look at this sample procedure:
(it simply prints its own name and parameters )
CREATE OR REPLACE PROCEDURE MY_PROC(ow in varchar2, tn IN varchar2)
IS
BEGIN
dbms_output.put_line('proc_name: '||$$PLSQL_UNIT||' parameters: '|| ow||' '||tn );
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERRORE: ' ||SQLERRM);
END MY_PROC;
/
Running procedure produces the following output:
SQL>
1 BEGIN
2 IBAD_OWN.MY_PROC('first_par', 'second_par');
3 END;
4 /
proc_name: MY_PROC parameters: first_par second_par
PL/SQL procedure successfully completed.
I'm not satisfy because I can't copy and paste in all my procedures because I have to hard code each procedure to set their right parameter variables.
Thanks in advance for the help.
It isn't possible to dynamically retrieve the values of parameters passed to a procedure in Oracle PL/SQL. The language simply isn't designed to handle this kind of operation.
Incidentally, in a procedure that is located within a package, $$PLSQL_UNIT will only return the package's name. I find it's better to define a consistently-named constant within each procedure that contains the procedure's name.
When I wanted the same functionality as yours I didn't find any good built-in solution.
What I did is: wrote DB-level trigger which modifies original body of function/procedure/package.
This trigger adds immediatly after "begin" dynamically generated piece of code from "user_arguments".
Plus, after that I include into this trigger the code, that logs calls of procs when exception occures.
Plus, you can trace procs calls, and many more interisting things.
But this solution works fine only for preproduction because performance decreases dramatically.
PS. Sorry for my bad English.

Oracle trigger to queue/dequeue doesn't fire

Oracle 11g.
I have a trigger which calls a procedure. That procedure performs queue and dequeue.
When I run the procedure in sqldeveloper, it runs fine.
If I update the tables which the trigger fires off, the procedure runs fine.
If I go into the application in question and run a process which causes the trigger to fire, the procedure doesn't work. Specifically the queue and dequeue don't work because if I remove the queue/dequeue code, and instead do an insert on a table, the procedure runs fine.
So I'm thinking that maybe there's a permissions problem causing the trigger unable to execute on dbms_aq. Does anyone know how to resolve this? Is there a user account that runs the triggers which needs to be given privileges?
I don't think the problem is with the procedure because it runs fine. I'm also not passing any dynamic data from the trigger so the way I run the procedure under sqldeveloper is exactly the same way the trigger is calling the procedure.
Note: sanitized code
create or replace
TRIGGER mytrig
AFTER INSERT ON INVENTORY_TRANSACTION
FOR EACH ROW
WHEN ((NEW.CLIENT = 'abcwidgets') AND (NEW.CODE = 'Receipt'))
BEGIN
dschema.mypack.queue_receipt('abcwidgets','999','bbb','1','88');
EXCEPTION
WHEN OTHERS THEN NULL;
END;
PROCEDURE QUEUE_RECEIPT(
CLIENTID IN VARCHAR2
, RECEIPTID IN VARCHAR2
, SITEID IN VARCHAR2
, LINEID IN VARCHAR2
, UPDATE_QTY IN VARCHAR2
) AS
ctxHandle dbms_xmlgen.ctxHandle;
l_xml xmltype;
queueopts DBMS_AQ.ENQUEUE_OPTIONS_T;
msgprops DBMS_AQ.MESSAGE_PROPERTIES_T;
msgid RAW(16);
BEGIN
ctxHandle := dbms_xmlgen.newContext('SELECT line_id,
user_data_4
FROM inventory
');
l_xml := xmltype(dbms_xmlgen.getxml(ctxHandle));
DBMS_AQ.ENQUEUE ('DSCHEMA.ABCWIDGETS_QUEUE',
queueopts,
msgprops,
l_xml,
msgid);
END QUEUE_RECEIPT;
You probably need to fully qualify the QUEUE NAME. The queue name namespace is determined at runtime, and with no public synonym on queues, it will look in the logged in user namespace first, regardless of the procedure owner. If I had to guess, when you login to run it in sqldeveloper, it is probably as the user that owns the procedure you are calling.
The problem was explained to me by a dba which was a bit over my head. There was some variable declarations in a package I was using that were declared outside the scope of a body. I moved these back into the package bodies and this caused them to start working.

Resources