• 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 =~ /^
    /) {
                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 = "
    ".$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
  • 相关阅读:
    JMETER-02-常用方法-全局变量,逻辑控制器,随机控制器,吞吐量控制器,加断言,事物控制器 ,循环控制器,仅一次控制器,foreach控制器
    接口自动化01接口基础-之接口的调用之postman和jmeter
    接口自动化01接口基础
    php中的9大缓存技术总结
    tp5自动生成目录
    PHP 服务器变量 $_SERVER
    从正则表达式的iUs说说模式修正符
    简单介绍下MYSQL的索引类型
    mysql几种存储引擎介绍
    PHP中return 和 exit 、break和contiue 区别与用法
  • 原文地址:https://www.cnblogs.com/PiscesCanon/p/13281926.html
Copyright © 2020-2023  润新知