Linux如何用腳本監控Oracle發送警告日志ORA-報錯發送郵件


 

Linux如何用腳本監控Oracle發送警告日志ORA-報錯發送郵件

前言

公司有購買的監控軟件北塔系統監控,由於購買的版權中只包含了有限台數據庫服務器的監控,所以只監控了比較重要的幾台服務器。

后邊出現過沒有監控的數據庫服務器表空間爆滿導致生產業務出現問題,后續手工處理數據也麻煩。

因此領導讓我想辦法能用什么方法監控上目前沒有監控的數據庫。

當然,我想到的只有三種,

  1. OEM 13C,Oracle本家的產品,好處多多,表空間使用率有但是警告日志監控方面不太清楚;
  2. 自己寫腳本監控,比較鍛煉人和實惠,功能比較單一;
  3. 第三方的監控軟件,鑒於北塔在數據庫方面的監控效果,本人不是看好第三方的而且沒有警告日志的監控。

搗鼓了幾天OEM 13C,最后公司暫時沒有資源裝新的OEM服務器,遂放棄。

自己寫腳本吧。。

 

思路

我的思路是:

  1. (步驟1)每次檢查的時候,截取警告日志中需要檢查的內容到另外的日志文件中(比如new_alert.log);
  2. (步驟2)過濾該日志文件(new_alert.log)中存在的ORA報錯信息,存放至另外的日志文件中(比如err_alert.log);
  3. (步驟3)將日志(err_alert.log)的報錯內容發送至指定的郵箱中,達到報警的目的。

下邊一步一步來寫腳本解決吧。

 

步驟1

首先我用的shell,腳本的例行開頭為:

#!/bin/bash
source /home/oracle/.bash_profile

 

 

然后需要考慮幾個問題,

  1. 怎么知道警告日志的路徑和警告日志名字,其實你可以固定在一個變量中。
    但是由於服務器不止一台,又想直接拷貝到其他服務器用的時候盡量少的改到腳本,因此我的想法是動態獲取路徑。
    警告日志名字的話當時就是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
  2.  

    日志找到了,檢查內容的起點和終點如何確定?
    終點好確定,就是警告日志的最后一行,那起點呢?
    當然是上一次檢查的終點那一行+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;
}
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) := '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
View Code

 

后續

由於創建了外部表,所以如果是在集群中有可能在數據庫數據庫自動收集統計信息的時候報錯如下:

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM