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 ########################################################################## #note:modiby by zhaokm on 20200713 #version:1.0 ########################################################################## 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 / || $line =~ /^KUP-/ || $line =~ /errors/ || $line =~ /^error/ ) { push @lines, $line; } } } my $tim = ""; my $len = @lines; for($i = $len-1; $i>=0; $i--) { if($lines[$i] =~ /^ORA-/||$lines[$i] =~ /^Errors/||$lines[$i] =~ /^KUP-/||$lines[$i] =~ /errors/||$lines[$i] =~ /^error/) { print $tim.$lines[$i]; $tim = ""; } else { $tim = "\n".$lines[$i]; } } close F; }
效果如下:
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) := 'problem@xxxxxxxx.xxx.xx'; p_recipient VARCHAR2(50) := 'problem@xxxxxxxx.xxx.xx'; p_subject VARCHAR2(100) := '某某數據庫(instance_name)存在ORA錯誤,請檢查!!!'; p_message VARCHAR2(32767) := '錯誤信息如下:'||chr(13)||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地址,我用的是公司私有的服務器,
所以不需要互聯網,各位根據自己的情況改。
還有就是不需要接收郵件的服務器地址,因為我只需要發送郵件即可,problem@xxxxxxxx.xxx.xx
發送給自己problem@xxxxxxxx.xxx.xx,之后你用比如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 eof
后續
由於創建了外部表,所以如果是在集群中有可能在數據庫數據庫自動收集統計信息的時候報錯如下:
Thu Jul 09 22:00:17 2020 DBMS_STATS: GATHER_STATS_JOB encountered errors. Check the trace file. Errors in file /u01/app/oracle/diag/rdbms/xxxx/xxxx2/trace/xxxx2_j002_20105.trc: ORA-20011: Approximate NDV failed: ORA-29913: error in executing ODCIEXTTABLEOPEN callout ORA-29400: data cartridge error error opening file /getORAerror/GETORAERROR_20105.log Sat Jul 11 06:00:18 2020 DBMS_STATS: GATHER_STATS_JOB encountered errors. Check the trace file. Errors in file /u01/app/oracle/diag/rdbms/xxxx/xxxx2/trace/xxxx2_j002_28237.trc: ORA-20011: Approximate NDV failed: ORA-29913: error in executing ODCIEXTTABLEOPEN callout ORA-29400: data cartridge error KUP-04040: file err_alert_xxxx1.log in GET_ORA not found
因此把外部表的統計信息鎖住,在觀察幾天看看。
注:我這個報錯是在另外一套集群,有兩個節點,所以有兩個外部表分別對應不同的節點。
因此,我需要鎖住兩個外部表的統計信息。單節點的庫暫時沒看到有報錯,建議也鎖住吧。
11:23:40 SYS@xxxx2(109)> exec dbms_stats.gather_table_stats(ownname => 'SYS',tabname => 'GETORAERROR1',estimate_percent => 100,method_opt => 'FOR ALL COLUMNS SIZE REPEAT'); BEGIN dbms_stats.gather_table_stats(ownname => 'SYS',tabname => 'GETORAERROR1',estimate_percent => 100,method_opt => 'FOR ALL COLUMNS SIZE REPEAT'); END; * ERROR at line 1: ORA-29913: error in executing ODCIEXTTABLEOPEN callout ORA-29400: data cartridge error KUP-04040: file err_alert_xxxx1.log in GET_ORA not found ORA-06512: at "SYS.DBMS_STATS", line 24281 ORA-06512: at "SYS.DBMS_STATS", line 24332 ORA-06512: at line 1 Elapsed: 00:00:01.38 11:24:16 SYS@xxxx2(109)> exec dbms_stats.lock_table_stats(ownname => 'SYS',tabname => 'GETORAERROR1'); PL/SQL procedure successfully completed. Elapsed: 00:00:00.12 11:24:24 SYS@xxxx2(109)> exec dbms_stats.gather_table_stats(ownname => 'SYS',tabname => 'GETORAERROR1',estimate_percent => 100,method_opt => 'FOR ALL COLUMNS SIZE REPEAT'); BEGIN dbms_stats.gather_table_stats(ownname => 'SYS',tabname => 'GETORAERROR1',estimate_percent => 100,method_opt => 'FOR ALL COLUMNS SIZE REPEAT'); END; * ERROR at line 1: ORA-20005: object statistics are locked (stattype = ALL) ORA-06512: at "SYS.DBMS_STATS", line 24281 ORA-06512: at "SYS.DBMS_STATS", line 24332 ORA-06512: at line 1 Elapsed: 00:00:00.02 11:24:31 SYS@xxxx2(109)> exec dbms_stats.lock_table_stats(ownname => 'SYS',tabname => 'GETORAERROR2'); PL/SQL procedure successfully completed. Elapsed: 00:00:00.01 12:16:58 SYS@xxxx1(86)> select owner,table_name,stattype_locked from dba_tab_statistics where table_name in ('GETORAERROR1','GETORAERROR2'); OWNER TABLE_NAME STATTYPE_LOCKED ------------------------- ------------------------------ --------------- SYS GETORAERROR2 ALL SYS GETORAERROR1 ALL Elapsed: 00:00:00.12