dinist

그누보드 sql_query 실행시 monolog 라이브러리를 활용한 에러로깅 본문

Web/PHP

그누보드 sql_query 실행시 monolog 라이브러리를 활용한 에러로깅

dinist 2023. 11. 8. 17:46

어떠한 글을 작성하면 DB에 저장이 되고, 메일로 알림을 발송하도록 되어있는 그누보드 기반 웹 사이트가 있다.

어느날 DB에는 저장이 되어 있지 않지만, 메일은 발송되었다는 연락이 있었다.

 

바로 테스트에 들어갔지만 DB에도 잘 저장되고 메일도 잘 전송되었다.

심지어 해당 문제 건만 DB에 저장되지 않았고 다른 사용자들의 게시글 저장과 메일 발송은 정상적이였다.

 

DB에 저장되지 않은 게시글의 발송 메일을 보니 sql 쿼리에 영향을 줄 내용은 찾지 못해 무엇이 원인인지는 파악하지 못하였으나 앞으로 이러한 일이 또 발생할 수 있으므로 쿼리에 오류가 발생한다면 로그를 남겨야할 필요성을 느꼈다.

 

 

.......
중략
.......


if(function_exists('mysqli_query') && G5_MYSQLI_USE) {
    if ($error) {
        $result = @mysqli_query($link, $sql) or die("<p>$sql<p>" . mysqli_errno($link) . " : " .  mysqli_error($link) . "<p>error file : {$_SERVER['SCRIPT_NAME']}");
    } else {
        try {
            $result = @mysqli_query($link, $sql);
        } catch (Exception $e) {
            $result = null;
        }
    }
} else {
    if ($error) {
        $result = @mysql_query($sql, $link) or die("<p>$sql<p>" . mysql_errno() . " : " .  mysql_error() . "<p>error file : {$_SERVER['SCRIPT_NAME']}");
    } else {
        $result = @mysql_query($sql, $link);
    }
}

.......
중략
.......


run_event('sql_query_after', $result, $sql, $start_time, $end_time, $error, $source);

 

위 코드는 그누보드의 common.lib.php 파일의 sql_query 함수의 일부이다.

쿼리 실행이 실패하면 false, 문제가 생긴다면 null을 리턴할 것이다.

 

코드 마지막에 sql_query_after라는 태그의 hook을 사용할 수 있도록 되어있다.

 

 

 

나는 /bbs 폴더에서 실행하는 모든 코드에 대한 sql_query 에러 로그를 남기고 싶었다.

그래서 /bbs/_common.php 파일에 hook을 추가하기로 결정

 

 

원래는 fopen fwrite로 아래 처럼 직접 로그를 남겨보려 했지만..!

<?php

....
중략
....

require_once G5_LIB_PATH."/vendor/autoload.php";

add_event('sql_query_after','sql_error_logger',NULL,6);

function sql_error_logger(...$params){
    if($params[0] === false || is_null($params[0])){
        $log_path = G5_DATA_PATH."/log/sql_error/";

        if(!is_dir($log_path))
            mkdir($log_path,G5_DIR_PERMISSION, true);

        $sql_error_resource = fopen($log_path.G5_TIME_YMD.".log","a");

        if($sql_error_resource){

            $result = array_slice($params,1);

            $time = G5_TIME_YMDHIS;
            $eol = PHP_EOL;

            $log = <<<LOG
            [$time] raw_sql : {$result[0]}
            startTimeStamp : {$result[1]} , endTimeStamp : {$result[2]}
            errorCode : {$result[3]['error_code']}, errorMessage : {$result[3]['error_message']}
            fileAt : {$result[4]['file']} , fileLine : {$result[4]['line']}{$eol}
            LOG;

            fwrite($sql_error_resource,$log);
            fclose($sql_error_resource);
        }
    }
}

 

제껴버리고 monolog라고 하는 로깅 라이브러리를 사용해보기로 했다.

해당 라이브러리 git에 있는 readme.md 를 참조하며 개발을 진행했다.

 

그리고 작업하다보니 로그를 남기는 부분을 heredoc으로 작성했는데 이는 PHP 7.3부터 지원하는 것이라

7.0을 사용하는 일부 사이트에서 문제가 있어 코드를 수정했다.

 

/**
 * @author dinist <dinist@naver.com>
 * sql_result false 일때만 실행 - 쿼리 실패할때만
 * 
 * 0 => result
 * 1 => sql
 * 2 => start_time
 * 3 => end_time
 * 4 => sql_error no & msg (relative arr)
 * 5 => stacktrace in first poped..! (relative arr)
 * 
 * 로깅 도중 array_slice로 인덱스가 하나씩 앞으로 당겨짐
 */
function sql_error_logger(...$params){

    if($params[0] === false || is_null($params[0])){
        $log_path = G5_DATA_PATH."/log/sql_error/";

        if(!is_dir($log_path))
            mkdir($log_path,G5_DIR_PERMISSION, true);

        $logger = new Logger("/bbs/*");

        $log_format = LoggerSetting::LOG_FORMAT;
        $rotate_handler = new RotatingFileHandler($log_path."bbs.log",60,Logger::ERROR);
        $rotate_handler->setFormatter(new LineFormatter($log_format,NULL,true));
        $rotate_handler->setFilenameFormat(LoggerSetting::FILE_NAME_FORMAT,LoggerSetting::DATE_FORMAT);
        $logger->pushHandler($rotate_handler);

        $result = array_slice($params,1);

        $log = "SQL : {$result[0]}".PHP_EOL;
        $log .= "startTimeStamp : {$result[1]} , endTimeStamp : {$result[2]}".PHP_EOL;
        $log .= "errorCode : {$result[3]['error_code']}, errorMessage : {$result[3]['error_message']}";

        $logger->error($log,['file_path'=>$result[4]['file'], 'file_line'=>$result[4]['line']]);

        $logger->close();
    }
}

 

 

LoggerSetting은 enum 타입이 아닌 추상클래스로 만들었다 (왜 추상클래스로 만들었냐면 인스턴스화 못하게 하려고!)

enum은 8.1부터 지원하므로....

 

<?php

abstract class LoggerSetting{
    const LOG_FORMAT = "[%level_name%][%datetime%] >> %message%".PHP_EOL."context : %context%".PHP_EOL;
    const FILE_NAME_FORMAT = "{filename}-{date}";
    const DATE_FORMAT = "Y-m-d";
}

 

LoggerSetting.php 파일은 /extend 디렉터리에 위치시킨다.

/common.php 파일을 실행하면서 /extend 디렉터리에 있는 모든 php 파일을 include 해주기 때문에 편하다.

 

 

에러 발생시 로그는 아래와 같이 기록된다.

 

'Web > PHP' 카테고리의 다른 글

PHP 폴더 복사 함수 코드  (0) 2023.07.24
[Laravel] - Eloquent에서 Enum 활용  (0) 2023.05.24
PHP - Enum 사용하기 2  (0) 2023.05.24
PHP - Enum 사용하기  (0) 2023.05.24
[PhpStorm] 라라벨 serve , npm dev 설정 추가  (0) 2023.01.17