Linux如何用指令碼監控Oracle傳送警告日誌ORA-報錯傳送郵件
Linux如何用指令碼監控Oracle傳送警告日誌ORA-報錯傳送郵件
前言
公司有購買的監控軟體北塔系統監控,由於購買的版權中只包含了有限臺數據庫伺服器的監控,所以只監控了比較重要的幾臺伺服器。
後邊出現過沒有監控的資料庫伺服器表空間爆滿導致生產業務出現問題,後續手工處理資料也麻煩。
因此領導讓我想辦法能用什麼方法監控上目前沒有監控的資料庫。
當然,我想到的只有三種,
- OEM 13C,Oracle本家的產品,好處多多;
- 自己寫指令碼監控,比較鍛鍊人和實惠,功能比較單一;
- 第三方的監控軟體,鑑於北塔在資料庫方面的監控效果,本人不是看好第三方的
搗鼓了幾天OEM 13C,最後公司暫時沒有資源裝新的OEM伺服器,遂放棄。
自己寫指令碼吧。。
思路
我的思路是:
- (步驟1)每次檢查的時候,擷取警告日誌中需要檢查的內容到另外的日誌檔案中(比如new_alert.log);
- (步驟2)過濾該日誌檔案(new_alert.log)中存在的ORA報錯資訊,存放至另外的日誌檔案中(比如err_alert.log);
- (步驟3)將日誌(err_alert.log)的報錯內容傳送至指定的郵箱中,達到報警的目的。
下邊一步一步來寫指令碼解決吧。
步驟1
首先我用的shell,指令碼的例行開頭為:
#!/bin/bash source /home/oracle/.bash_profile
然後需要考慮幾個問題,
- 怎麼知道警告日誌的路徑和警告日誌名字,其實你可以固定在一個變數中。
但是由於伺服器不止一臺,又想直接拷貝到其他伺服器用的時候儘量少的改到指令碼,因此我的想法是動態獲取路徑。
警告日誌名字的話當時就是alert_$ORACLE_SID.log了。
因此我是這麼做的,查詢警告日誌路徑然後賦值給變數dir:
# 查詢alert日誌所在的路徑 sqlplus -s /nolog &> /dev/null << EOF set feedback off heading off verify off trimspool on timing off set pagesize 0 linesize 300 conn / as sysdba; set timing off set time off spool /tmp/tmpdir.txt select value from v\$parameter where name='background_dump_dest'; spool off exit; EOF #是否成功獲取到路徑 errs=`grep 'ERROR' /tmp/tmpdir.txt | wc -l` if [ $errs -gt 0 ]; then echo "query alert log direction run error, please check the /tmp/tmpdir.txt for details." exit 1 else dir=`cat /tmp/tmpdir.txt` fi
-
日誌找到了,檢查內容的起點和終點如何確定?
終點好確定,就是警告日誌的最後一行,那起點呢?
當然是上一次檢查的終點那一行+1。
因此,我需要用多一個檔案儲存上次的終點行數,該檔案用/tmp/rownum.log儲存。
首先判斷是否存在檔案/tmp/rownum.log,不存在則表示第一次執行警告日誌檢查,因此肯定是從第一行開始到最後一行結束全部都檢查一遍。##如果檔案不存在,則建立,並且將數字"1"儲存至檔案中表示從第1行開始獲取檢查
##如果檔案存在,不會執行if fi程式碼塊中的內容 if [ ! -f "/tmp/rownum.log" ];then touch /tmp/rownum.log echo 1 > /tmp/rownum.log fi之後起點用變數row1表示,終點用變數row2表示。
因此
row1=`sed -n '1p' /tmp/rownum.log`
row2=`wc -l $dir/alert_$ORACLE_SID.log |awk '{print $1}'`
這裡考慮一個問題,如果警告日誌被備份走了,比如我會不定時mv alert_test.log alert_test.log.20200711避免日誌過大等問題。
如果日誌被備份走了,那麼新的日誌在本次檢查中肯定都是需要檢查的,因此,
用變數text1儲存上次檢查最後一行的文字內容值/tmp/rownum.log,變數text2儲存當前警告日誌的第$row1行的文字內容,如果有,
$text1!=$text2,表示日誌被備份移動走,這時候將row1重置為1表示新產生的日誌檔案中要從第1行開始檢查。
最終如下:#獲取上次檢查點,該檢查點為這次日誌檢查的起點 row1=`sed -n '1p' /tmp/rownum.log` text1=`sed -n '2p' /tmp/rownum.log` text2=`sed -n ''$row1'p' $dir/alert_$ORACLE_SID.log`
##$text1!=$text2,表示日誌被備份移動走,相等則不會執行if fi程式碼塊中的內容 if [ "$text1" != "$text2" ]; then row1=1 fi
row2=`wc -l $dir/alert_$ORACLE_SID.log |awk '{print $1}'`另外,如果上次檢查和這次檢查期間,沒有日誌產生,則直接退出shell即可。
##若是相等表示間隔檢查期間無新日誌產生 if [ "$row1" == "$row2" ]; then exit 1 fi
然後開始更新/tmp/rownum.log中的記錄。
把此次檢查的終點更新進去,+1之後表示下次檢查的起點echo $row2 > /tmp/rownum.log sed -n ''$row2'p' $dir/alert_$ORACLE_SID.log >> /tmp/rownum.log
然後獲取起點到終點的內容,儲存至前文所說的new_alert.log。
這裡我還是用alert_$ORACLE_SID.log表示,就不用new_alert.log了。
##獲取文字 row1=$((row1+1)) sed -n "${row1},${row2}p" $dir/alert_$ORACLE_SID.log > /getORAerror/alert_$ORACLE_SID.log
至此,步驟1完成。
步驟2
有個perl指令碼check_alert.pl,專門取ORA錯誤的包括前後資訊的。
#!/usr/bin/perl use POSIX; my $alert = $ARGV[0]; my $days = $ARGV[1]; &checkAlert($alert, $days); sub checkAlert { my $fileName = shift; my $days = shift; my $t = localtime; $t -= $days*60*1440; my $stop = strftime "%a %b %d", localtime(time-$days*60*1440); my @lines; open F, $fileName or die "can not open $fileName, $!"; my $i = -1; my $line; while(seek F, --$i, 2) { $line = <F>; if($line =~ /^\n/) { seek F, $i+1, 2; $line = <F>; if($line =~ /^$stop/) { last; } if($line =~ /^ORA-/ || $line =~ /^Mon / || $line =~ /^Tue / || $line =~ /^Wed / || $line =~ /^Thu / || $line =~ /^Fri / || $line =~ /^Sat / || $line =~ /^Sun / || $line =~ /^Errors / ) { push @lines, $line; } } } my $tim = ""; my $len = @lines; for($i = $len-1; $i>=0; $i--) { if($lines[$i] =~ /^ORA-/||$lines[$i] =~ /^Errors/) { print $tim.$lines[$i]; $tim = ""; } else { $tim = "\n".$lines[$i]; } } close F; }View Code
效果如下:
Fri Sep 13 11:00:55 2019 Errors in file /app/oracle/diag/rdbms/xxxxxxxxxx/xxxxxxxxxx1/trace/xxxxxxxxxx1_lgwr_15763.trc: ORA-00313: open failed for members of log group 1 of thread 1 ORA-00312: online log 1 thread 1: '+ARCH/xxxxxxxxxx/onlinelog/group_1.487.974760013' ORA-17503: ksfdopn:2 Failed to open file +ARCH/xxxxxxxxxx/onlinelog/group_1.487.974760013 ORA-15012: ASM file '+ARCH/xxxxxxxxxx/onlinelog/group_1.487.974760013' does not exist ORA-00312: online log 1 thread 1: '+DATA/xxxxxxxxxx/onlinelog/group_1.265.974760013' ORA-17503: ksfdopn:2 Failed to open file +DATA/xxxxxxxxxx/onlinelog/group_1.265.974760013 ORA-15012: ASM file '+DATA/xxxxxxxxxx/onlinelog/group_1.265.974760013' does not exist Errors in file /app/oracle/diag/rdbms/xxxxxxxxxx/xxxxxxxxxx1/trace/xxxxxxxxxx1_lgwr_15763.trc: ORA-00313: open failed for members of log group 1 of thread 1 ORA-00312: online log 1 thread 1: '+ARCH/xxxxxxxxxx/onlinelog/group_1.487.974760013' ORA-17503: ksfdopn:2 Failed to open file +ARCH/xxxxxxxxxx/onlinelog/group_1.487.974760013 ORA-15012: ASM file '+ARCH/xxxxxxxxxx/onlinelog/group_1.487.974760013' does not exist ORA-00312: online log 1 thread 1: '+DATA/xxxxxxxxxx/onlinelog/group_1.265.974760013' ORA-17503: ksfdopn:2 Failed to open file +DATA/xxxxxxxxxx/onlinelog/group_1.265.974760013 ORA-15012: ASM file '+DATA/xxxxxxxxxx/onlinelog/group_1.265.974760013' does not exist
步驟3
首先獲取錯誤資訊,然後判斷是否有錯誤資訊生成。
沒有錯誤資訊生成,則退出指令碼,不繼續執行。
#獲取錯誤資訊 /usr/bin/perl /getORAerror/check_alert.pl /getORAerror/alert_$ORACLE_SID.log 365 > /getORAerror/err_alert_$ORACLE_SID.log ##判斷是否有ORA錯誤 err_row=`wc -l /getORAerror/err_alert_$ORACLE_SID.log |awk '{print $1}'` if [ $err_row -eq 0 ]; then exit 1 fi
接下來,所有的錯誤資訊都在檔案err_alert_$ORACLE_SID.log中,只需要將該檔案中的內容傳送到郵箱即可。
第一可以用Linux系統本身的客戶端,比如mail/sendmail/mutt等命令,不過要求需要連通網際網路,並且傳送騰訊郵箱是接收不了的。
但是可以用163郵箱是可以接收的,這裡有我2016年的時候用mail命令傳送郵件的記錄。
具體命令的用法問度娘,比較簡單的。
第二種就是伺服器是在內網,無法訪問網際網路,我現在就是這種情況。
而且我們監控用的郵箱就是騰訊企業郵箱,也無法接收。
那麼我採用的方法是用Oracle自身發郵件的功能,這個比第一種麻煩。
首先Oracle資料庫需要訪問到你的err_alert_$ORACLE_SID.log檔案內容,我建立外部表進行訪問。
15:34:30 SYS@xxxxxxxxxx(714)> create directory get_ORA as '/getORAerror'; Directory created. Elapsed: 00:00:00.02 15:51:19 SYS@xxxxxxxxxx(714)> create table getORAerror 15:51:37 2 (message nvarchar2(400)) 15:51:37 3 organization external 15:51:37 4 (type ORACLE_LOADER default directory get_ORA location('err_alert_xxxxxxxxxx.log')); Table created. Elapsed: 00:00:00.03
之後利用儲存過程,採用遊標訪問外部表,傳送郵件就行。
儲存過程主體網上就有,利用Oracle資料庫傳送郵件,根據我自己的環境我改造了下。
CREATE OR REPLACE PROCEDURE send_mail IS v_mailhost VARCHAR2(30) := 'xxx.xx.xx.xx'; v_user VARCHAR2(30) := 'problem'; v_pass VARCHAR2(20) := 'problem'; v_sender VARCHAR2(50) := '[email protected]'; p_recipient VARCHAR2(50) := '[email protected]'; p_subject VARCHAR2(100) := '某某資料庫(instance_name)存在ORA錯誤,請檢查!!!'; p_message VARCHAR2(32767) := '錯誤資訊如下:'||chr(13); p_tmp VARCHAR2(400) := ''; v_conn UTL_SMTP. connection ; v_msg varchar2(32767); cursor data_query_cur is select message from getORAerror; BEGIN open data_query_cur; loop fetch data_query_cur into p_tmp; exit when data_query_cur%notfound; p_message := p_message||p_tmp ||chr(13); end loop; close data_query_cur; v_conn := UTL_SMTP.open_connection(v_mailhost, 25); UTL_SMTP.ehlo(v_conn, v_mailhost); UTL_SMTP.command(v_conn, 'AUTH LOGIN' ); UTL_SMTP.command(v_conn,UTL_RAW.cast_to_varchar2(UTL_ENCODE.base64_encode(UTL_RAW.cast_to_raw(v_user)))); UTL_SMTP.command(v_conn,UTL_RAW.cast_to_varchar2(UTL_ENCODE.base64_encode(UTL_RAW.cast_to_raw(v_pass)))); UTL_SMTP.mail(v_conn, v_sender); UTL_SMTP.rcpt(v_conn, p_recipient); v_msg := 'Date:' || TO_CHAR(SYSDATE, 'dd mon yy hh24:mi:ss' ) || UTL_TCP.CRLF || 'From: ' || '<' || v_sender || '>' || UTL_TCP.CRLF || 'To: ' || '<' || p_recipient || '>' || UTL_TCP.CRLF || 'Subject: ' || p_subject || UTL_TCP.CRLF || UTL_TCP.CRLF || p_message; UTL_SMTP.open_data(v_conn); UTL_SMTP.write_raw_data(v_conn, UTL_RAW.cast_to_raw(v_msg)); UTL_SMTP.close_data(v_conn); UTL_SMTP.quit(v_conn); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.put_line(DBMS_UTILITY.format_error_stack); DBMS_OUTPUT.put_line(DBMS_UTILITY.format_call_stack); END send_mail; /
注意,變數p_message是正文,就是你的ORA得錯誤存放變數,一開始我傳送郵件之後,
全部的資訊都在一行亂掉了,後邊用chr(13)進行回車換行。
注意,chr(10)表示換行 chr(13)表示回車。好像跟C語言是一樣的。
另外,v_mailhost表示傳送郵件的伺服器地址,應該也是SMTP地址,我用的是公司私有的伺服器,
所以不需要網際網路,各位根據自己的情況改。
還有就是不需要接收郵件的伺服器地址,因為我只需要傳送郵件即可,[email protected]
傳送給自己[email protected],之後你用比如Foxmail登陸就能夠收到(Foxmail配置了另外接收郵件的伺服器)。
最後,就一開始的shell指令碼,加上呼叫儲存過程發郵件的部分就行。
sqlplus / as sysdba <<eof begin send_mail; end; / exit eof
全部有兩個指令碼,一個步驟2中的指令碼。
另外一個就是步驟1中一直說明的指令碼,比較分散,這個統一一下內容。
#!/bin/bash source /home/oracle/.bash_profile ##如果檔案不存在,則建立 if [ ! -f "/tmp/rownum.log" ];then touch /tmp/rownum.log echo 1 > /tmp/rownum.log fi # 查詢alert日誌所在的路徑 sqlplus -s /nolog &> /dev/null << eof set feedback off heading off verify off trimspool on timing off set pagesize 0 linesize 300 conn / as sysdba; set timing off set time off spool /tmp/tmpdir.txt select value from v\$parameter where name='background_dump_dest'; spool off exit; eof errs=`grep 'ERROR' /tmp/tmpdir.txt | wc -l` if [ $errs -gt 0 ]; then echo "query alert log direction run error, please check the /tmp/tmpdir.txt for details." exit 1 else dir=`cat /tmp/tmpdir.txt` fi #獲取上次檢查點,該檢查點為這次日誌檢查的起點 row1=`sed -n '1p' /tmp/rownum.log` text1=`sed -n '2p' /tmp/rownum.log` text2=`sed -n ''$row1'p' $dir/alert_$ORACLE_SID.log` ##比較上次檢查時最後一行和最後一行對應行號來對應當前行的文字是否一致 ##一致表示警告日誌沒有被歸檔 if [ "$text1" != "$text2" ]; then row1=1 fi row2=`wc -l $dir/alert_$ORACLE_SID.log |awk '{print $1}'` ##若是相等表示間隔檢查期間無新日誌產生 if [ "$row1" == "$row2" ]; then exit 1 fi echo $row2 > /tmp/rownum.log sed -n ''$row2'p' $dir/alert_$ORACLE_SID.log >> /tmp/rownum.log ##獲取新增的警告日誌內容 row1=$((row1+1)) sed -n "${row1},${row2}p" $dir/alert_$ORACLE_SID.log > /getORAerror/alert_$ORACLE_SID.log #獲取錯誤資訊 /usr/bin/perl /getORAerror/check_alert.pl /getORAerror/alert_$ORACLE_SID.log 365 > /getORAerror/err_alert_$ORACLE_SID.log ##判斷是否有ORA錯誤 err_row=`wc -l /getORAerror/err_alert_$ORACLE_SID.log |awk '{print $1}'` if [ $err_row -eq 0 ]; then exit 1 fi sqlplus / as sysdba <<eof begin send_mail; end; / exit eofView Code
&n