I have scheduled a PL/SQL procedure. I want to send the status of the PL/SQL procedure (whether it is successful or has any error messages) to my email address.
I saw some ways of sending pre-defined templates of emails using UTL_MAIL. But how can I get the status of my procedure into an email?
Send an e-mail at the end of the scheduled stored procedure, e.g.
create or replace procedure p_your_proc as
l_error varchar2(300);
begin
-- do some processing
-- if there were no errors
utl_mail.send(sender => 'rosh#gmail.com',
recipients => 'rosh#gmail.com',
cc => null,
bcc => null,
subject => 'Procedure P_YOUR_PROC completed successfully',
message => null);
exception
when others then
l_error := sqlerrm;
utl_mail.send(sender => 'rosh#gmail.com',
recipients => 'rosh#gmail.com',
cc => null,
bcc => null,
subject => 'Procedure P_YOUR_PROC ended with an error',
message => l_error);
raise;
end;
Related
I have the following Oracle queue, subscriber, and registration created:
begin
dbms_aqadm.create_queue_table(
queue_table => 'test_queue',
multiple_consumers => true,
queue_payload_type => 'sys.aq$_jms_map_message',
compatible => '8.1.3',
comment => 'Creating test queue table'
);
dbms_aqadm.create_queue(
queue_name => 'test_queue',
queue_table => 'test_queue',
comment => 'Test Queue'
);
dbms_aqadm.start_queue(queue_name => 'test_queue');
dbms_aqadm.add_subscriber(
queue_name => 'test_queue',
subscriber => sys.aq$_agent('plsql', null, null)
);
dbms_aq.register(
reg_list => sys.aq$_reg_info_list(
sys.aq$_reg_info(
'test_queue:plsql',
dbms_aq.namespace_aq,
'plsql://p_queue_callback?PR=1',
null
)
),
reg_count => 1
);
end;
Given a callback procedure that has an uncaught exception:
create or replace procedure p_queue_callback (
context raw,
reginfo sys.aq$_reg_info,
descr sys.aq$_descriptor,
payload varchar2,
payloadl number
)
is
l_foo number;
begin
-- Results in an ORA-01476: divisor is equal to zero runtime exception
l_foo := 1 / 0;
end;
Is an error logged or visible anywhere once a message is enqueued and the procedure is called? Or do callback procedures just silently fail?
https://docs.oracle.com/cd/B19306_01/server.102/b14257/aq_views.htm
USER_QUEUE_SCHEDULES/DBA_QUEUE_SCHEDULES? There are 3 columns for errors.
I m trying to crate notification to send email whenever job is broken. This is the job:
declare
jobno number;
begin
dbms_job.submit( jobno,
'test_job_procedure;',
SYSDATE,
'SYSDATE + 1/24 /12');
commit;
end;
This is configuration of credentials :
BEGIN
DBMS_SCHEDULER.create_credential (credential_name => 'MAILSERVER_CREDENTIAL',
username => 'test#gmail.com',
password => 'test');
END;
BEGIN
DBMS_SCHEDULER.set_scheduler_attribute ('email_server', 'smtp.gmail.com:587');
DBMS_SCHEDULER.set_scheduler_attribute ('email_sender', 'test#gmail.com');
DBMS_SCHEDULER.set_scheduler_attribute ('email_server_credential', 'MAILSERVER_CRED
ENTIAL');
END;
And this is email notification scheduler: Instead of what there was job_name but in dbms_job job names are numbers which are non consistent, when I restart the job it gets new number so I put WHAT as something that will recognize that job. I don't know if this could work because I am getting error at the end but anyway when i run this select
SELECT *
FROM all_scheduler_global_attribute
I get this results https://imgur.com/a/FnQJ7
And this is job email notification :
BEGIN
DBMS_SCHEDULER.ADD_JOB_EMAIL_NOTIFICATION (
what => 'test_job_procedure',
recipients => 'test1#gmail.com',
sender => 'test#gmail.com',
subject => 'Scheduler Job Notification',
body => '%event_type% occurred at %event_timestamp%. %error_message%',
events => 'JOB_FAILED, JOB_BROKEN');
END;
can someone please go through this and tell me where am I making mistakes ?
I'm currently trying to implement a similar version of oracle's APEX_MAIL package. I have everything working, but I can't make the job work unless I modify it.
The job APEX_MAIL uses is called ORACLE_APEX_MAIL_QUEUE
BEGIN
DBMS_SCHEDULER.set_attribute( name => '"APEX_040000"."ORACLE_APEX_MAIL_QUEUE"', attribute => 'job_action', value => 'APEX_040000.WWV_FLOW_MAIL.PUSH_QUEUE');
DBMS_SCHEDULER.set_attribute( name => '"APEX_040000"."ORACLE_APEX_MAIL_QUEUE"', attribute => 'number_of_arguments', value => '2');
DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(
job_name => '"APEX_040000"."ORACLE_APEX_MAIL_QUEUE"',
argument_position => 1,
argument_value => '');
DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(
job_name => '"APEX_040000"."ORACLE_APEX_MAIL_QUEUE"',
argument_position => 2,
argument_value => '');
END;
/
So I go to the package to see what the code does. I'm was assuming push queue would send out emails in the queue. Instead, it calls the same job again!
PROCEDURE PUSH_QUEUE( P_SMTP_HOSTNAME IN VARCHAR2 DEFAULT NULL,
P_SMTP_PORTNO IN VARCHAR2 DEFAULT NULL )
IS
BEGIN
PUSH_QUEUE_BACKGROUND;
END PUSH_QUEUE;
PROCEDURE PUSH_QUEUE_BACKGROUND
IS
BEGIN
SYS.DBMS_SCHEDULER.RUN_JOB( JOB_NAME => 'ORACLE_APEX_MAIL_QUEUE', USE_CURRENT_SESSION => FALSE );
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE <> -27478 THEN
RAISE;
END IF;
END PUSH_QUEUE_BACKGROUND;
So basically this job does nothing, but I switch it to call PUSH_QUEUE_IMMEDIATE which does what I think it should do.
PROCEDURE PUSH_QUEUE_IMMEDIATE( P_FORCE_YN IN VARCHAR2 DEFAULT 'N')
IS
L_STATUS NUMBER;
L_LOCK_HDL VARCHAR2(128);
E_DB_SHUTDOWN EXCEPTION;
PRAGMA EXCEPTION_INIT(E_DB_SHUTDOWN, -1089);
BEGIN
WWV_FLOW_DEBUG.ENABLE_DBMS_OUTPUT;
SYS.DBMS_LOCK.ALLOCATE_UNIQUE( LOCKNAME => 'APEX_MAIL_QUEUE_LOCK', LOCKHANDLE => L_LOCK_HDL);
L_STATUS := SYS.DBMS_LOCK.REQUEST( LOCKHANDLE => L_LOCK_HDL,
LOCKMODE => SYS.DBMS_LOCK.X_MODE,
TIMEOUT => 0,
RELEASE_ON_COMMIT => FALSE );
WWV_FLOW_DEBUG.INFO('APEX Mail Lock status: ' || L_STATUS );
IF L_STATUS = 0 THEN
FOR C1 IN ( SELECT ID, MAIL_SEND_COUNT, LAST_UPDATED_ON
FROM WWV_FLOW_MAIL_QUEUE
ORDER BY MAIL_SEND_COUNT, LAST_UPDATED_ON) LOOP
BEGIN
WWV_FLOW_DEBUG.INFO( 'Pushing email: ' || C1.ID );
IF (C1.MAIL_SEND_COUNT = 0) OR (NVL(P_FORCE_YN,'N') = 'Y') OR
(C1.MAIL_SEND_COUNT > 0 AND (POWER(2,C1.MAIL_SEND_COUNT)/(60*24) + C1.LAST_UPDATED_ON) < SYSDATE) THEN
BACKGROUND( P_ID => C1.ID );
END IF;
WWV_FLOW_DEBUG.INFO( 'Pushed email: ' || C1.ID );
EXCEPTION
WHEN OTHERS THEN
WWV_FLOW_DEBUG.LOG_EXCEPTION;
IF L_LOCK_HDL IS NOT NULL THEN
L_STATUS := SYS.DBMS_LOCK.RELEASE( L_LOCK_HDL );
WWV_FLOW_DEBUG.INFO('APEX Mail released lock' );
END IF;
END;
END LOOP;
END IF;
IF L_LOCK_HDL IS NOT NULL THEN
L_STATUS := SYS.DBMS_LOCK.RELEASE( L_LOCK_HDL );
WWV_FLOW_DEBUG.INFO('APEX Mail released lock' );
END IF;
EXCEPTION WHEN E_DB_SHUTDOWN THEN
NULL;
END PUSH_QUEUE_IMMEDIATE;
I'm trying to copy APEX_MAIL to a point, but if I do, I won't have a working job. Can anyone point out if APEX_MAIL changes what the job does after an application setting change or any other change?
Thanks in advance!
APEX_MAIL.PUSH_QUEUE is usable in your own code to send your mail (in the queue) out immediate. The job normally calls PUSH_QUEUE_IMMEDIATE. I don't know if your setting ever was a bug in the installation or something wrong on your site.
Thus fact, it calls PUSH_QUEUE_IMMEDIATE in a separate session as APEX_040000 job.
Since everyone can request an immediate send of all the jobs in the queue, it makes sure via SYS.DBMS_LOCK.REQUEST only one session will actually do the transmit.
I'm trying to see if the following would be possible.
it all hinges on creating Policies and Procedures for each row..
Is it possible to create a dynamically named Procedure and pass that name to a dynamically
named policy?
So as following create these in a loop ??
I have an email alert
CREATE OR REPLACE PROCEDURE caps_email_alert (sch varchar2, tab varchar2, pol varchar2)
AS
msg varchar2(20000) := 'CAPS.ME_PAYEE table violation. The time is: ';
BEGIN
msg := msg||to_char(SYSDATE, 'Day DD MON, YYYY HH24:MI:SS');
UTL_MAIL.SEND (
sender => 'me#somewhere.com',
recipients => 'me#somewhere.com',
subject => 'Table modification on caps.me_payee',
message => msg);
END caps_email_alert;
/
and a policy that will call it...
BEGIN
DBMS_FGA.ADD_POLICY (
object_schema => 'CAPS',
object_name => 'ME_PAYEE',
policy_name => 'CHK_CAPS_ME_PAYEE',
audit_column => 'CARRIER_NO',
audit_condition => 'CARRIER_NO = ''20'' ',
handler_schema => 'SYSADMIN_FGA',
handler_module => 'CAPS_EMAIL_ALERT',
enable => TRUE,
statement_types => 'SELECT, UPDATE',
audit_trail => DBMS_FGA.DB + DBMS_FGA.EXTENDED);
END;
/
I would need to write a subprogram that would execute as follows...
( to create a Procedure and Policy for each row )
BEGIN
FOR c IN (SELECT schema as sch, table as tab, cond as pred FROM slac_red_table) LOOP
--here need to create dynamically named procedure and pass it to a dynamically named policy
--so a function that creates a dynamically named procedure
emailerProcedureName emailerProcedureName%TYPE := emailerFunction(c.sch, c.tab)
createPolicyFunction(c.sch, c.tab, c.cond,emailerProcedureName)
END LOOP;
END;
Maybe I can accomplish the same some other way... have you encountered something similar ?
I can't seem to find the solution to my problem, I've been stuck at this for hours.
I'm usings Oracle AQs:
Dbms_Aqadm.Create_Queue_Table(Queue_Table => 'ITEM_EVENT_QT',
Queue_Payload_Type => 'ITEM_EVENT',
Multiple_Consumers => TRUE);
Dbms_Aqadm.Create_Queue(Queue_Name => 'ITEM_EVENT_QUEUE',
Queue_Table => 'ITEM_EVENT_QT',
Max_Retries => 5,
Retry_Delay => 0,
Retention_Time => 432000, -- 5 DAYS
Dependency_Tracking => FALSE,
COMMENT => 'Item Event Queue');
-- START THE QUEUE
Dbms_Aqadm.Start_Queue('ITEM_EVENT_QUEUE');
-- GRANT QUEUE PRIVILEGES
Dbms_Aqadm.Grant_Queue_Privilege(Privilege => 'ALL',
Queue_Name => 'ITEM_EVENT_QUEUE',
Grantee => 'PUBLIC',
Grant_Option => FALSE);
END;
Here's one of my subscribers:
Dbms_Aqadm.Add_Subscriber(Queue_Name => 'ITEM_EVENT_QUEUE',
Subscriber => Sys.Aq$_Agent('ITEM_SUBSCRIBER_1',
NULL,
NULL),
rule => 'tab.user_data.header.thread_no = 1');
Dbms_Aq.Register(Sys.Aq$_Reg_Info_List(Sys.Aq$_Reg_Info('ITEM_EVENT_QUEUE:ITEM_SUBSCRIBER_1',
Dbms_Aq.Namespace_Aq,
'plsql://ITEM_API.GET_QUEUE_FROM_QUEUE',
HEXTORAW('FF'))),1);
The subscriber registration:
Whenever a certain event occurs on my DB, I'm using a trigger to add "the event" to my AQ by calling the following procedure from my ITEM_API package:
PROCEDURE ADD_EVENT_TO_QUEUE(I_EVENT IN ITEM_EVENT,
O_STATUS_CODE OUT VARCHAR2,
O_ERROR_MSG OUT VARCHAR2) IS
ENQUEUE_OPTIONS DBMS_AQ.ENQUEUE_OPTIONS_T;
MESSAGE_PROPERTIES DBMS_AQ.MESSAGE_PROPERTIES_T;
MESSAGE_HANDLE RAW(16);
EVENT ITEM_EVENT;
HEADER_PROP HEADER_PROPERTIES;
BEGIN
EVENT := I_EVENT;
EVENT.SEQ_NO := ITEM_EVENT_SEQ.NEXTVAL;
ENQUEUE_OPTIONS.VISIBILITY := DBMS_AQ.ON_COMMIT;
ENQUEUE_OPTIONS.SEQUENCE_DEVIATION := NULL;
MESSAGE_PROPERTIES.PRIORITY := 1;
MESSAGE_PROPERTIES.DELAY := DBMS_AQ.NO_DELAY;
MESSAGE_PROPERTIES.EXPIRATION := DBMS_AQ.NEVER;
HEADER_PROP := HEADER_PROPERTIES(1);
EVENT.HEADER := HEADER_PROP;
DBMS_AQ.ENQUEUE(QUEUE_NAME => 'ITEM_EVENT_QUEUE',
ENQUEUE_OPTIONS => ENQUEUE_OPTIONS,
MESSAGE_PROPERTIES => MESSAGE_PROPERTIES,
PAYLOAD => EVENT,
MSGID => MESSAGE_HANDLE);
EXCEPTION
WHEN OTHERS THEN
ERROR_HANDLER.LOG_ERROR(NULL,
EVENT.ITEM,
EVENT.SEQ_NO,
SQLCODE,
SQLERRM,
O_STATUS_CODE,
O_ERROR_MSG);
RAISE;
END ADD_EVENT_TO_QUEUE;
And it's working because when I check my AQ table, I can find "the event", however my dequeue method is not dequeing, as you can see in the image bellow, there's no DEQ_TIME.
Here's my dequeue method, also from my ITEM_API package:
PROCEDURE GET_QUEUE_FROM_QUEUE(CONTEXT RAW,
REGINFO SYS.AQ$_REG_INFO,
DESCR SYS.AQ$_DESCRIPTOR,
PAYLOAD RAW,
PAYLOADL NUMBER) IS
R_DEQUEUE_OPTIONS DBMS_AQ.DEQUEUE_OPTIONS_T;
R_MESSAGE_PROPERTIES DBMS_AQ.MESSAGE_PROPERTIES_T;
V_MESSAGE_HANDLE RAW(16);
I_PAYLOAD ITEM_EVENT;
L_PROC_EVENT BOOLEAN;
O_TARGETS CFG_EVENT_STAGE_TBL;
O_ERROR_MSG VARCHAR2(300);
O_STATUS_CODE VARCHAR2(100);
BEGIN
R_DEQUEUE_OPTIONS.MSGID := DESCR.MSG_ID;
R_DEQUEUE_OPTIONS.CONSUMER_NAME := DESCR.CONSUMER_NAME;
R_DEQUEUE_OPTIONS.DEQUEUE_MODE := DBMS_AQ.REMOVE;
--R_DEQUEUE_OPTIONS.WAIT := DBMS_AQ.NO_WAIT;
DBMS_AQ.DEQUEUE(QUEUE_NAME => DESCR.QUEUE_NAME,
DEQUEUE_OPTIONS => R_DEQUEUE_OPTIONS,
MESSAGE_PROPERTIES => R_MESSAGE_PROPERTIES,
PAYLOAD => I_PAYLOAD,
MSGID => V_MESSAGE_HANDLE);
IF I_PAYLOAD IS NOT NULL THEN
L_PROC_EVENT := PROCESS_EVENT(I_PAYLOAD,
O_TARGETS,
O_STATUS_CODE,
O_ERROR_MSG);
END IF;
EXCEPTION
WHEN OTHERS THEN
ERROR_HANDLER.LOG_ERROR(NULL,
NULL,
NULL,
SQLCODE,
SQLERRM,
O_STATUS_CODE,
O_ERROR_MSG);
RAISE;
END GET_QUEUE_FROM_QUEUE;
Am I doing something wrong? How can I fix this? I think there might be a problem with my subscriber registration, but I'm not sure.
EDIT: I've just figured out that if I remove the subscribers and the register, and then re-add them, they'll dequeue all messages. Howerver if another event gets enqueued, it stays there indefinetly (or until I remove and add the subscribers again):
The record with state 0 and no DEQ_TIME is the new one.
Do I need a scheduler or something like that?
EDIT: I've added a scheduler propagation to my AQ:
DBMS_AQADM.SCHEDULE_PROPAGATION('ITEM_EVENT_QUEUE');
and even added the next_time field:
DBMS_AQADM.SCHEDULE_PROPAGATION('ITEM_EVENT_QUEUE', SYSDATE + 30/86400);
Still doesn't work. Any suggestions? I guess the AQ Notifications aren't working, and my callback procedure is never called. How can I fix this?
EDIT: I've removed my procedure from the package just for testing purposes, so my team mates can compile the ITEM_API package (I don't know if recompiling the package, may or may not have impacts on the dequeue process).
Still doesn't work.
Create a code block and run the following:
DECLARE
dequeue_options DBMS_AQ.dequeue_options_t;
message_properties DBMS_AQ.message_properties_t;
message_handle RAW (16);
I_PAYLOAD ITEM_EVENT;
no_messages exception;
msg_content VARCHAR2 (4000);
PRAGMA EXCEPTION_INIT (no_messages, -25228);
BEGIN
dequeue_options.wait := DBMS_AQ.NO_WAIT;
dequeue_options.consumer_name := 'ITEM_SUBSCRIBER_1';
dequeue_options.navigation := DBMS_AQ.FIRST_MESSAGE;
LOOP
DBMS_AQ.DEQUEUE (queue_name => 'ITEM_EVENT_QUEUE',
dequeue_options => dequeue_options,
message_properties => message_properties,
payload => I_PAYLOAD,
msgid => message_handle
);
END LOOP;
EXCEPTION
WHEN no_messages
THEN
DBMS_OUTPUT.PUT_LINE ('No more messages left');
END;
Let me know what happens to your enqueued messages.
You should have a table where you're dequing the data.
Can you also try adding the enqueud table in the agent and then specify the agent to the dequeue table.
DECLARE
aSubscriber sys.aq$_agent;
BEGIN
aSubscriber := sys.aq$_agent('ITEM_SUBSCRIBER_1',
'ITEM_EVENT_QUEUE',
0);
dbms_aqadm.add_subscriber
( queue_name => 'ITEM_EVENT_QUEUE'
,subscriber => aSubscriber);
END;
/
We faced a related problem (at least related to the title), we couldn't dequeue messages with a delay. The messages in the queue stayed the state "WAITING". And were not changed to "READY".
The Oracle AQ monitoring process that is responsable for changing the state from "WAITING" to "READY" (after the delay is expired) wasn't working properly.
For us a database restart fixed this issue.
I faced the same problem, but it was solved after changing these 2 DB parameters:
job_queue_processes (must be > than 0)
aq_tm_processes (autotuning)
Hope it helps.