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 =; if($line =~ /^\n/) { seek F, $i+1, 2; $line = ; 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) := '[email protected]'; p_recipient VARCHAR2(50) := '[email protected]'; 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地址,我用的是公司私有的服务器,
所以不需要互联网,各位根据自己的情况改。
还有就是不需要接收邮件的服务器地址,因为我只需要发送邮件即可,[email protected]
发送给自己[email protected],之后你用比如Foxmail登陆就能够收到(Foxmail配置了另外接收邮件的服务器)。
最后,就一开始的shell脚本,加上调用存储过程发邮件的部分就行。
sqlplus / as sysdba <;
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