IT/DevOps,기타 Tools

md 파일과 Python-VScode를 통해 리눅스 보고서 자동화

xego 2025. 3. 12. 15:54

이 자동화는 리눅스에서 Shell을 통해 데이터를 뽑아 낼수 있다는 가정 하에 진행되고 있습니다.

기본적인 md파일 구조는 아래와 같은 양식을 통해 생성하였습니다.

 

WAS서버

vi md.sh

#!/bin/bash
report_date=$(date +%Y.%m)
last_month=$(date -d "$current_date -1 month" +%Y-%m)
start_date="${last_month}-01"
end_date=$(date -d "${last_month}-01 +1 month -1 day" +%Y-%m-%d)

# DB별 경로 설정
DATABASES=("test_egg" "test_rice")

# 하나의 보고서 파일 생성
for db in "${DATABASES[@]}"; do
    if [ "$db" = "testegg" ]; then
        cat << EOF > all_was_report1.md
EOF
    else
        echo -e "\n\n" >> all_was_report1.md
    fi

    # URL 생성 시 "test" 접두사 제거
    service_name=${db#test}

    cat << EOF >> all_was_report.md
# ${db}
서비스 URL : https://food.${service_name}.or.kr
서비스 기간 :  ${start_date} ~ ${end_date}
WAS 디스크 사용량 :  $(du -sh /data/${db} | awk '{print $1}')
NAS 디스크 사용량 : $(du -sh /nas/${db} | awk '{print $1}')
WAS 로그 크기 : $(du -sh /data/${db}/apache-tomcat-9.0.97/logs/ | awk '{print $1}')
APP 로그 크기 : $(du -sh /data/${db}/log/ | awk '{print $1}')
EOF
done

 

 

 

DB서버

#!/bin/bash

report_date=$(date +%Y.%m)
last_month=$(date -d "$current_date -1 month" +%Y-%m)
start_date="${last_month}-01"
end_date=$(date -d "${last_month}-01 +1 month -1 day" +%Y-%m-%d)
# DB별 접속정보 설정
MYSQL="mysql -pTest#123 -h localhost"
DATABASES=("testegg" "testrice")
BACKUP_ROOT="/db_backup/DB"


# 사람이 읽기 쉬운 단위 변환 함수
convert_size() {
    local size=$1
    if [ "$size" -lt 1024 ]; then
        echo "${size} B"
    elif [ "$size" -lt $((1024 * 1024)) ]; then
        echo "$(awk "BEGIN {printf \"%.1f\", $size/1024}") KB"
    elif [ "$size" -lt $((1024 * 1024 * 1024)) ]; then
        echo "$(awk "BEGIN {printf \"%.1f\", $size/(1024*1024)}") MB"
    else
        echo "$(awk "BEGIN {printf \"%.1f\", $size/(1024*1024*1024)}") GB"
    fi
}



# 각 DB 정보 수집 및 작성
for db in "${DATABASES[@]}"; do
    if [ "$db" = "testegg" ]; then
            cat << EOF > all_db_report.md
EOF
    else
         echo -e "\n\n" >> all_db_report.md
    fi

    # DB 정보 수집
    MYSQL_CMD="$MYSQL -u$db"
    table_count=$($MYSQL_CMD -N -e "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_TYPE LIKE 'base_table' AND TABLE_SCHEMA = '${db}';")
    proc_count=$($MYSQL_CMD -N -e "SELECT COUNT(*) FROM information_schema.ROUTINES WHERE ROUTINE_TYPE='PROCEDURE' AND ROUTINE_SCHEMA='${db}';")
    view_count=$($MYSQL_CMD -N -e "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_TYPE LIKE 'view' AND TABLE_SCHEMA = '${db}';")
    func_count=$($MYSQL_CMD -N -e "SELECT COUNT(*) FROM information_schema.ROUTINES WHERE ROUTINE_TYPE='FUNCTION' AND ROUTINE_SCHEMA='${db}';")
    db_usage=$(du -sh /data/mysql/${db} | awk '{print $1}')

    # 접속 로그 수집
    menu_logs=$($MYSQL_CMD -N $db -e "SELECT COUNT(*) FROM SYS_USR_EXECINFO;")
    user_logs=$($MYSQL_CMD -N $db -e "SELECT COUNT(*) FROM SYS_USR_VISITINFO;")

    # 직원 수 조회 쿼리
    user_count=$($MYSQL_CMD -N $db -e "SELECT COUNT(*) AS cnt,
        sum(case when fn_nvl(EMP_NO,'') = '' then 0 ELSE 1 END) AS yes_cnt,
        sum(case when fn_nvl(EMP_NO,'') = '' then 1 ELSE 0 END) AS no_cnt
        FROM sys_usr WHERE USE_YN='Y';")

    # 결과를 배열로 분리
    read -r total emp_yes emp_no <<< "$user_count"

    # 백업 관련 정보 수집
    backup_info=$(find ${BACKUP_ROOT} -type f \( -iname "*.sql" -o -iname "*.dat" \) -name "*_${db}_*" -exec ls --time-style=long-iso -l {} \; | sort -k 6,7 | tail -1)
    backup_date=$(echo "$backup_info" | awk '{gsub("-", ".", $6); print $6}')
    backup_date_formatted=$(echo "$backup_date" | sed 's/\.//g') # yyyy.mm.dd -> yyyymmdd 변환

    # 해당 날짜의 백업 파일 총 크기 계산
    daily_backup_size_raw=$(du -cb ${BACKUP_ROOT}/*${backup_date_formatted}_${db}_* 2>/dev/null | tail -1 | awk '{print $1}')
    daily_backup_size=$(convert_size ${daily_backup_size_raw:-0})


    # 결과 작성
    cat << EOF >> all_db_report.md
# ${db} db
Tables : ${table_count}개
Procedures : ${proc_count}개
Views : ${view_count}개
Functions : ${func_count}개
DB 디스크 사용량 : ${db_usage}
메뉴 접속 건수 : ${menu_logs}건
사용자 접속 건수 : ${user_logs}건
총 사용자 수 : ${total}명
직원 수 : ${emp_yes}명
기타 사용자 수 : ${emp_no}명
최근 백업일 : ${backup_date}
일일 백업 크기 : ${daily_backup_size}
EOF

   # 일별 접속자 데이터 수집 및 작성
   $MYSQL_CMD -N $db -e "
   SELECT
       DATE_FORMAT(RECENT_LOGIN_DTM, '%Y-%m-%d') as visit_date,
       COUNT(DISTINCT USR_ID) as visit_count
   FROM sys_usr_visitinfo
   WHERE RECENT_LOGIN_DTM BETWEEN '${start_date}' AND '${end_date} 23:59:59'
   GROUP BY DATE_FORMAT(RECENT_LOGIN_DTM, '%Y-%m-%d')
   ORDER BY visit_date;" | while read -r date count; do
       echo "${date} : ${count}" >> all_db_report.md
   done
done

 

 

 

 

베스천서버

#!/bin/bash

# 사용자로부터 sudo 비밀번호 입력 받기
echo "Please enter your sudo password: "
read -s SUDO_PASSWORD

# 명령어 실행 함수
execute_command() {
  local server=$1
  local command=$2
  echo "Connecting to $server and executing command..."

  # 비밀번호를 echo로 전달하여 sudo 명령에 입력
  ssh -i /home/test/.ssh/id_rsa test@$server "echo $SUDO_PASSWORD | sudo -S su -c '$command'"

  echo "Command executed on $server."
}

# 각 서버에 대해 명령 실행
# WAS 접속 및 실행
execute_command "1.1.1.2" "cd /data/WAS_BACKUP/bin/ && ./md.sh"
scp -i /home/test/.ssh/id_rsa test@1.1.1.2:/data/WAS_BACKUP/bin/all_was_report.md ./

# DB 접속 및 실행
execute_command "1.1.1.3" "cd /db_backup/bin/ && ./md.sh"
scp -i /home/test/.ssh/id_rsa test@1.1.1.3:/db_backup/bin/all_db_report.md ./


cd /Report/bin
cat all_was_report.md all_db_report.md  > all_report.md

echo "All tasks completed."

 

md파일...은 아니여도 꼭 상관은 없다. 보고서 자동화를 위한 데이터만 뽑히면 됨.


Python Release Python 3.13.2 | Python.org

 

Python Release Python 3.13.2

The official home of the Python Programming Language

www.python.org

Python 설치해주자. 꼭 " Add Python to PATH" 옵션을 체크하자!!!!

 

Download Visual Studio Code - Mac, Linux, Windows

 

Download Visual Studio Code - Mac, Linux, Windows

Visual Studio Code is free and available on your favorite platform - Linux, macOS, and Windows. Download Visual Studio Code to experience a redefined code editor, optimized for building and debugging modern web and cloud applications.

code.visualstudio.com

VScode 설치해주자


설치가 끝났다면 환경 세팅을 해보자.

 

VScode 프로그램을 켜서  왼쪽의 조각모음처럼 생긴걸 누르자

 

설치가 끝났다면 터미널을 켜서 설정을 확인해보자

pip install pywin32 python-dateutil 명령어를 쳐보자

아래와 같은 문제가 발생한다면 Win + R을 눌러 아래 명령어를 쳐주자

C:\Users\User\AppData\Local\Programs\Python\Python313\Scripts\pip.exe install pywin32 python-dateutil

 

추후 import win32com.client as win32 가 필요함으로 해줘야한다.

설치가 되는것을 볼수있다.


운영보고서를 작성해보자.

보고서 할 내용들을 가지고 템플릿 변수 처리하도록 하자.


py 파일도 만들어주도록 하자

import re
import win32com.client as win32
import os
import sys
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta

def read_data_from_md(filename):
    """MD 파일을 읽어 내용 반환"""
    with open(filename, 'r', encoding='utf-8') as f:
        content = f.read()
    return content

def get_institution_name(org_name):
    """기관명 매핑"""
    institution_mapping = {
        'test_soycrab': '간장게장센터',
        'test_chilicrab': '양념게장공사',
        'test_egg': '계란후라이협회',
        'test_air': '공기밥진리위원회',
    }
    return institution_mapping.get(org_name, org_name)

def format_number(number_str):
    """숫자 문자열에 3자리마다 콤마를 추가하는 함수"""
    try:
        return format(int(number_str), ',')
    except ValueError:
        return number_str

def parse_organization_data(content, org_name):
    """기관별 데이터 파싱"""
    org_pattern = f"# {org_name}\n(.*?)(?=\n\n|$)"
    org_match = re.search(org_pattern, content, re.DOTALL)

    db_pattern = f"# {org_name} db\n(.*?)(?=\n\n|$)"
    db_match = re.search(db_pattern, content, re.DOTALL)

    data = {}
    data['INSTITUTION'] = get_institution_name(org_name)

    # 전월 마지막 날짜 계산
    today = datetime.now()
    first_day_of_this_month = today.replace(day=1)
    last_day_of_last_month = first_day_of_this_month - timedelta(days=1)
    last_month_year = last_day_of_last_month.year
    last_month = last_day_of_last_month.month
    data['LONGMO'] = "31" if last_day_of_last_month.day == 31 else ""
    data['DAY31'] = "0" if last_day_of_last_month.day == 31 else ""
    for day in range(1, 31):
        data['DAY' + str(day)] = 0

    if org_match:
        org_info = org_match.group(1)
        data['SERVICE_TIME'] = re.search(r'서비스 기간 :  (.*)', org_info).group(1).strip()
        data['SERVICE_URL'] = re.search(r'서비스 URL : (.*)', org_info).group(1).strip()
        data['WAS_DISK_USED'] = re.search(r'WAS 디스크 사용량 :  (.*)', org_info).group(1).strip()
        data['NAS_DISK_USED'] = re.search(r'NAS 디스크 사용량 : (.*)', org_info).group(1).strip()
        data['WAS_LOG'] = re.search(r'WAS 로그 크기 : (.*)', org_info).group(1).strip()
        data['APP_LOG'] = re.search(r'APP 로그 크기 : (.*)', org_info).group(1).strip()

    if db_match:
        db_info = db_match.group(1)
        data['TABLE'] = format_number(re.search(r'Tables : (\d+)', db_info).group(1).strip())
        data['PROCEDURES'] = format_number(re.search(r'Procedures : (\d+)', db_info).group(1).strip())
        data['VIEWS'] = format_number(re.search(r'Views : (\d+)', db_info).group(1).strip())
        data['FUNCTIONS'] = format_number(re.search(r'Functions : (\d+)', db_info).group(1).strip())
        data['DB_USED'] = re.search(r'DB 디스크 사용량 : (.*)', db_info).group(1).strip()
        data['EMP'] = format_number(re.search(r'직원 수 : (\d+)', db_info).group(1).strip())
        data['OTHER_EMP'] = format_number(re.search(r'기타 사용자 수 : (\d+)', db_info).group(1).strip())
        data['TOTAL_EMP'] = format_number(re.search(r'총 사용자 수 : (\d+)', db_info).group(1).strip())
        data['MENU_LOG'] = format_number(re.search(r'메뉴 접속 건수 : (\d+)', db_info).group(1).strip())
        data['USER_COUNT'] = format_number(re.search(r'사용자 접속 건수 : (\d+)', db_info).group(1).strip())

        data['BACKUP_DAY'] = re.search(r'최근 백업일 : (.*)', db_info).group(1).strip()
        data['BACKUP_TOTAL'] = re.search(r'일일 백업 크기 : (.*)', db_info).group(1).strip()

        daily_pattern = r'(\d{4}-\d{2}-\d{2})\s*:\s*(\d+)'
        daily_matches = re.findall(daily_pattern, db_info)
        
        for day in range(1, 32):
            data[f'DAY{day}'] = "0"
            
        for date_str, count in daily_matches:
            date = datetime.strptime(date_str, '%Y-%m-%d')
            if date.year == last_month_year and date.month == last_month:
                data[f'DAY{date.day}'] = count
                
        if last_day_of_last_month.day == 31:
            data['LONGMO'] = "31"
            data['DAY31'] = data['DAY31']
        else:
            data['LONGMO'] = ""
            data['DAY31'] = ""

    return data

def create_hwp_report(template_path, output_path, data):
    """한글 보고서 생성"""
    try:
        hwp = win32.gencache.EnsureDispatch("HWPFrame.HwpObject")
        hwp.RegisterModule("FilePathCheckDLL", "FilePathCheckerModule")
        
        hwp.Open(os.path.abspath(template_path))
        
        for key, value in data.items():
            field = f"{{{{{key}}}}}"
            hwp.HAction.GetDefault("AllReplace", hwp.HParameterSet.HFindReplace.HSet)
            option = hwp.HParameterSet.HFindReplace
            option.FindString = field
            option.ReplaceString = str(value)
            option.IgnoreMessage = 1
            option.Direction = 0
            option.WholeWordOnly = 1
            option.ReplaceMode = 1
            hwp.HAction.Execute("AllReplace", hwp.HParameterSet.HFindReplace.HSet)
        
        hwp.SaveAs(os.path.abspath(output_path))
        
        hwp.Quit()
        
    except Exception as e:
        print(f"에러 발생: {str(e)}")
        if 'hwp' in locals():
            hwp.Quit()

def main():
    """메인 함수"""
    current_date = datetime.now()
    last_month = current_date - relativedelta(months=1)
    last_month_str = last_month.strftime("%m")
    
    organizations = [
        'test_간장게장',
        'test_양념게장',
        'test_계란후라이',
        'test_공기밥',
    ]
    
    current_dir = os.path.dirname(os.path.abspath(sys.argv[0]))  # exe가 위치한 디렉토리 경로
    
    # MD 파일 경로
    md_file = os.path.join(current_dir, 'Report-Data.md')
    template_file = os.path.join(current_dir, 'report-template.hwp')
    
    output_dir = os.path.join(current_dir, 'reports')
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    content = read_data_from_md(md_file)
    
    for org in organizations:
        try:
            print(f'{org} 보고서 생성 시작...')
            data = parse_organization_data(content, org)
            print(data)
            output_filename = f"{get_institution_name(org)}_{last_month_str}월_운영보고서.hwp"
            output_path = os.path.join(output_dir, output_filename)
            create_hwp_report(template_file, output_path, data)
            print(f'{get_institution_name(org)} 보고서 생성 완료')
        except Exception as e:
            print(f'{org} 보고서 생성 중 오류 발생: {str(e)}')

if __name__ == '__main__':
    main()

리눅스 서버에서 운영 리포트에 필요한 sh을 구성하고, md파일로 데이터를 추출해주고

경로 설정을 해주자.

VScode서 run python file 을 통해 

잘 생성되는것을 볼수있다.

 

 

 

 

 

 

 

 

 

 


이후 조금 더 보완하였다.

 

서비스운영결과와 서비스현황에 대해 목표수준을 자동으로 계산하도록 하였다.

    # 서비스 기간의 시작일과 종료일 파싱
    if 'SERVICE_TIME' in data:
        service_time = data['SERVICE_TIME']  # "2025-02-01 ~ 2025-02-28" 형식
        start_date_str, end_date_str = service_time.split(" ~ ")
        start_date = datetime.strptime(start_date_str, "%Y-%m-%d")
        end_date = datetime.strptime(end_date_str, "%Y-%m-%d")
    
    # 서비스 일수 계산
    service_days = (end_date - start_date).days + 1
    
    # 가용률 관련 값 계산
    total_hours = service_days * 24  # 총 서비스 시간 (일 * 24시간)
    data['AVAILABILITY_STANDARD_HOURS'] = str(total_hours)  # 전체 서비스 시간
    data['AVAILABILITY_STANDARD_RATE'] = "99.5%"  # SLA 기준값
    
    # 목표 시간 계산 (전체 시간의 99.5%)
    target_hours = round(total_hours * 0.995)  # 99.5%를 소수점으로 변환하여 계산
    data['AVAILABILITY_TARGET_HOURS'] = str(target_hours)  # 목표수준 시간
    
    # 백업 준수율 관련 값 계산
    data['BACKUP_STANDARD_TIMES'] = str(service_days)  # 매일 백업한다고 가정