프로젝트 해답
1. 마크다운 파서 솔루션
1.1 JavaScript 구현
const patterns = {
headers: /^(#{1,6})\s+(.+)$/gm,
links: /\[([^\]]+)\]\(([^)]+)\)/g,
bold: /\*\*([^*]+)\*\*/g,
italic: /\*([^*]+)\*/g
};
function parse(markdown) {
let html = markdown;
// 헤더 변환
html = html.replace(patterns.headers, (match, hashes, text) => {
const level = hashes.length;
return `<h${level}>${text}</h${level}>`;
});
// 링크 변환
html = html.replace(patterns.links,
(match, text, url) => `<a href="${url}">${text}</a>`);
// 강조 구문 변환 (순서 중요: bold를 먼저 처리)
html = html.replace(patterns.bold,
(match, text) => `<strong>${text}</strong>`);
html = html.replace(patterns.italic,
(match, text) => `<em>${text}</em>`);
return html;
}
// 사용 예시
const markdown = `
# 메인 제목
## 부제목
이것은 [위니브](https://weniv.co.kr) 튜토리얼입니다.
**중요한** 내용과 *강조할* 내용이 있습니다.
`;
console.log(parse(markdown));
const patterns = {
headers: /^(#{1,6})\s+(.+)$/gm,
links: /\[([^\]]+)\]\(([^)]+)\)/g,
bold: /\*\*([^*]+)\*\*/g,
italic: /\*([^*]+)\*/g
};
function parse(markdown) {
let html = markdown;
// 헤더 변환
html = html.replace(patterns.headers, (match, hashes, text) => {
const level = hashes.length;
return `<h${level}>${text}</h${level}>`;
});
// 링크 변환
html = html.replace(patterns.links,
(match, text, url) => `<a href="${url}">${text}</a>`);
// 강조 구문 변환 (순서 중요: bold를 먼저 처리)
html = html.replace(patterns.bold,
(match, text) => `<strong>${text}</strong>`);
html = html.replace(patterns.italic,
(match, text) => `<em>${text}</em>`);
return html;
}
// 사용 예시
const markdown = `
# 메인 제목
## 부제목
이것은 [위니브](https://weniv.co.kr) 튜토리얼입니다.
**중요한** 내용과 *강조할* 내용이 있습니다.
`;
console.log(parse(markdown));
1.2 Python 구현
import re
patterns = {
'headers': re.compile(r'^(#{1,6})\s+(.+)$', re.MULTILINE),
'links': re.compile(r'\[([^\]]+)\]\(([^)]+)\)'),
'bold': re.compile(r'\*\*([^*]+)\*\*'),
'italic': re.compile(r'\*([^*]+)\*')
}
def parse(markdown):
html = markdown
# 헤더 변환
def header_replace(m):
level = len(m.group(1))
return f'<h{level}>{m.group(2)}</h{level}>'
html = patterns['headers'].sub(header_replace, html)
# 링크 변환
html = patterns['links'].sub(r'<a href="\2">\1</a>', html)
# 강조 구문 변환
html = patterns['bold'].sub(r'<strong>\1</strong>', html)
html = patterns['italic'].sub(r'<em>\1</em>', html)
return html
# 사용 예시
markdown = """
# 메인 제목
## 부제목
이것은 [위니브](https://weniv.co.kr) 튜토리얼입니다.
**중요한** 내용과 *강조할* 내용이 있습니다.
"""
print(parse(markdown))
import re
patterns = {
'headers': re.compile(r'^(#{1,6})\s+(.+)$', re.MULTILINE),
'links': re.compile(r'\[([^\]]+)\]\(([^)]+)\)'),
'bold': re.compile(r'\*\*([^*]+)\*\*'),
'italic': re.compile(r'\*([^*]+)\*')
}
def parse(markdown):
html = markdown
# 헤더 변환
def header_replace(m):
level = len(m.group(1))
return f'<h{level}>{m.group(2)}</h{level}>'
html = patterns['headers'].sub(header_replace, html)
# 링크 변환
html = patterns['links'].sub(r'<a href="\2">\1</a>', html)
# 강조 구문 변환
html = patterns['bold'].sub(r'<strong>\1</strong>', html)
html = patterns['italic'].sub(r'<em>\1</em>', html)
return html
# 사용 예시
markdown = """
# 메인 제목
## 부제목
이것은 [위니브](https://weniv.co.kr) 튜토리얼입니다.
**중요한** 내용과 *강조할* 내용이 있습니다.
"""
print(parse(markdown))
2. 로그 파일 분석기 솔루션
2.1 JavaScript 구현
간단히 실습해볼 수 있는 코드부터 살펴보도록 하겠습니다.
const logLine = `32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"`;
// 정규표현식 패턴
const pattern = /(\d+\.\d+\.\d+\.\d+).*\[(\d+\/\w+\/\d+:(\d+):\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"/;
// 매칭 수행
const match = logLine.match(pattern);
// 변수 할당
const [fullMatch, ip, timestamp, hour, method, path, referer] = match;
// 결과 출력
console.log('전체 매칭:', fullMatch);
console.log('IP 주소 (match[1]):', ip);
console.log('타임스탬프 (match[2]):', timestamp);
console.log('시간 (match[3]):', hour);
console.log('HTTP 메서드 (match[4]):', method);
console.log('경로 (match[5]):', path);
console.log('리퍼러 (match[6]):', referer);
const logLine = `32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"`;
// 정규표현식 패턴
const pattern = /(\d+\.\d+\.\d+\.\d+).*\[(\d+\/\w+\/\d+:(\d+):\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"/;
// 매칭 수행
const match = logLine.match(pattern);
// 변수 할당
const [fullMatch, ip, timestamp, hour, method, path, referer] = match;
// 결과 출력
console.log('전체 매칭:', fullMatch);
console.log('IP 주소 (match[1]):', ip);
console.log('타임스탬프 (match[2]):', timestamp);
console.log('시간 (match[3]):', hour);
console.log('HTTP 메서드 (match[4]):', method);
console.log('경로 (match[5]):', path);
console.log('리퍼러 (match[6]):', referer);
const data = `32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"
187.234.121.55 - - [29/Nov/2024:08:16:12 +0900] "GET /course/python HTTP/1.1" 200 3267 "https://books.weniv.co.kr/" "Mozilla/5.0"
91.45.234.178 - - [29/Nov/2024:08:17:01 +0900] "POST /shorten HTTP/1.1" 201 342 "https://weniv.link/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:18:23 +0900] "GET /schedule HTTP/1.1" 200 2891 "https://time.weniv.co.kr/" "Safari/605.1.15"
211.89.145.67 - - [29/Nov/2024:08:19:45 +0900] "GET / HTTP/1.1" 200 1523 "https://weniv.co.kr" "Firefox/121.0"
167.89.234.90 - - [29/Nov/2024:08:20:11 +0900] "GET /dashboard HTTP/1.1" 200 5632 "https://canvas.weniv.co.kr/" "Edge/120.0.0.0"
45.178.234.89 - - [29/Nov/2024:08:21:34 +0900] "POST /query HTTP/1.1" 200 892 "https://sql.weniv.co.kr/editor" "Chrome/120.0.0.0"
123.45.167.89 - - [29/Nov/2024:08:22:56 +0900] "GET /book/python-basics HTTP/1.1" 200 4521 "https://books.weniv.co.kr/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:23:12 +0900] "GET /stats HTTP/1.1" 200 2341 "https://weniv.link/dashboard" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:24:45 +0900] "GET /calendar/2024 HTTP/1.1" 200 3421 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
178.90.234.156 - - [29/Nov/2024:08:25:23 +0900] "POST /contact HTTP/1.1" 200 567 "https://weniv.co.kr/about" "Edge/120.0.0.0"
90.234.156.78 - - [29/Nov/2024:08:26:34 +0900] "GET /assignments HTTP/1.1" 200 4532 "https://canvas.weniv.co.kr/" "Firefox/121.0"
145.67.89.234 - - [29/Nov/2024:08:27:56 +0900] "GET /practice HTTP/1.1" 200 3421 "https://sql.weniv.co.kr/learn" "Chrome/120.0.0.0"
67.89.234.156 - - [29/Nov/2024:08:28:12 +0900] "GET /ebook/javascript HTTP/1.1" 200 5632 "https://books.weniv.co.kr/" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:29:45 +0900] "POST /shorten/custom HTTP/1.1" 201 445 "https://weniv.link/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:30:23 +0900] "GET /reminder HTTP/1.1" 200 2341 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:31:34 +0900] "GET /courses HTTP/1.1" 200 6789 "https://weniv.co.kr/learning" "Edge/120.0.0.0"
78.90.234.156 - - [29/Nov/2024:08:32:56 +0900] "POST /submit HTTP/1.1" 200 892 "https://canvas.weniv.co.kr/" "Safari/605.1.15"
90.234.156.78 - - [29/Nov/2024:08:33:12 +0900] "GET /advanced HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/courses" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:34:45 +0900] "GET /shop HTTP/1.1" 200 3421 "https://books.weniv.co.kr/" "Chrome/120.0.0.0"
`;
function analyzeLogs(logData) {
// 정규표현식 패턴 - Referer 정보 포함
const pattern = /(\d+\.\d+\.\d+\.\d+).*\[(\d+\/\w+\/\d+:(\d+):\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"/;
// 결과를 저장할 객체
const results = {
ipCounts: {},
hourCounts: {},
serviceCounts: {}
};
function extractService(referer) {
if (!referer || referer === '-') {
return 'direct';
}
const servicePatterns = [
{ pattern: /sql\.weniv\.co\.kr/, name: '위니브 SQL' },
{ pattern: /books\.weniv\.co\.kr/, name: '위니북스' },
{ pattern: /weniv\.link/, name: '위니브 링크' },
{ pattern: /time\.weniv\.co\.kr/, name: '위니브 타이머' },
{ pattern: /canvas\.weniv\.co\.kr/, name: '위니브 캔버스' },
{ pattern: /weniv\.co\.kr/, name: '위니브 메인' }
];
const service = servicePatterns.find(s => s.pattern.test(referer));
return service ? service.name : 'other';
}
// 각 로그 라인 분석
const lines = logData.split('\n').filter(line => line.trim());
lines.forEach(line => {
const match = line.match(pattern);
if (match) {
const [_, ip, timestamp, hour, method, path, referer] = match;
// IP 주소별 카운트
results.ipCounts[ip] = (results.ipCounts[ip] || 0) + 1;
// 시간대별 카운트
results.hourCounts[hour] = (results.hourCounts[hour] || 0) + 1;
// 서비스별 카운트
const service = extractService(referer);
results.serviceCounts[service] = (results.serviceCounts[service] || 0) + 1;
}
});
return results;
}
function printResults(results) {
console.log("=== IP 주소별 접속 횟수 ===");
Object.entries(results.ipCounts)
.sort(([,a], [,b]) => b - a)
.forEach(([ip, count]) => {
console.log(`${ip}: ${count}회`);
});
console.log("\n=== 시간대별 접속 통계 ===");
Object.entries(results.hourCounts)
.sort(([a], [b]) => Number(a) - Number(b))
.forEach(([hour, count]) => {
console.log(`${hour}시: ${count}회`);
});
console.log("\n=== 서비스별 접속 통계 TOP 5 ===");
Object.entries(results.serviceCounts)
.sort(([,a], [,b]) => b - a)
.slice(0, 5)
.forEach(([service, count]) => {
console.log(`${service}: ${count}회`);
});
}
// 사용 예시
const results = analyzeLogs(data);
printResults(results);
const data = `32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"
187.234.121.55 - - [29/Nov/2024:08:16:12 +0900] "GET /course/python HTTP/1.1" 200 3267 "https://books.weniv.co.kr/" "Mozilla/5.0"
91.45.234.178 - - [29/Nov/2024:08:17:01 +0900] "POST /shorten HTTP/1.1" 201 342 "https://weniv.link/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:18:23 +0900] "GET /schedule HTTP/1.1" 200 2891 "https://time.weniv.co.kr/" "Safari/605.1.15"
211.89.145.67 - - [29/Nov/2024:08:19:45 +0900] "GET / HTTP/1.1" 200 1523 "https://weniv.co.kr" "Firefox/121.0"
167.89.234.90 - - [29/Nov/2024:08:20:11 +0900] "GET /dashboard HTTP/1.1" 200 5632 "https://canvas.weniv.co.kr/" "Edge/120.0.0.0"
45.178.234.89 - - [29/Nov/2024:08:21:34 +0900] "POST /query HTTP/1.1" 200 892 "https://sql.weniv.co.kr/editor" "Chrome/120.0.0.0"
123.45.167.89 - - [29/Nov/2024:08:22:56 +0900] "GET /book/python-basics HTTP/1.1" 200 4521 "https://books.weniv.co.kr/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:23:12 +0900] "GET /stats HTTP/1.1" 200 2341 "https://weniv.link/dashboard" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:24:45 +0900] "GET /calendar/2024 HTTP/1.1" 200 3421 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
178.90.234.156 - - [29/Nov/2024:08:25:23 +0900] "POST /contact HTTP/1.1" 200 567 "https://weniv.co.kr/about" "Edge/120.0.0.0"
90.234.156.78 - - [29/Nov/2024:08:26:34 +0900] "GET /assignments HTTP/1.1" 200 4532 "https://canvas.weniv.co.kr/" "Firefox/121.0"
145.67.89.234 - - [29/Nov/2024:08:27:56 +0900] "GET /practice HTTP/1.1" 200 3421 "https://sql.weniv.co.kr/learn" "Chrome/120.0.0.0"
67.89.234.156 - - [29/Nov/2024:08:28:12 +0900] "GET /ebook/javascript HTTP/1.1" 200 5632 "https://books.weniv.co.kr/" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:29:45 +0900] "POST /shorten/custom HTTP/1.1" 201 445 "https://weniv.link/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:30:23 +0900] "GET /reminder HTTP/1.1" 200 2341 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:31:34 +0900] "GET /courses HTTP/1.1" 200 6789 "https://weniv.co.kr/learning" "Edge/120.0.0.0"
78.90.234.156 - - [29/Nov/2024:08:32:56 +0900] "POST /submit HTTP/1.1" 200 892 "https://canvas.weniv.co.kr/" "Safari/605.1.15"
90.234.156.78 - - [29/Nov/2024:08:33:12 +0900] "GET /advanced HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/courses" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:34:45 +0900] "GET /shop HTTP/1.1" 200 3421 "https://books.weniv.co.kr/" "Chrome/120.0.0.0"
`;
function analyzeLogs(logData) {
// 정규표현식 패턴 - Referer 정보 포함
const pattern = /(\d+\.\d+\.\d+\.\d+).*\[(\d+\/\w+\/\d+:(\d+):\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"/;
// 결과를 저장할 객체
const results = {
ipCounts: {},
hourCounts: {},
serviceCounts: {}
};
function extractService(referer) {
if (!referer || referer === '-') {
return 'direct';
}
const servicePatterns = [
{ pattern: /sql\.weniv\.co\.kr/, name: '위니브 SQL' },
{ pattern: /books\.weniv\.co\.kr/, name: '위니북스' },
{ pattern: /weniv\.link/, name: '위니브 링크' },
{ pattern: /time\.weniv\.co\.kr/, name: '위니브 타이머' },
{ pattern: /canvas\.weniv\.co\.kr/, name: '위니브 캔버스' },
{ pattern: /weniv\.co\.kr/, name: '위니브 메인' }
];
const service = servicePatterns.find(s => s.pattern.test(referer));
return service ? service.name : 'other';
}
// 각 로그 라인 분석
const lines = logData.split('\n').filter(line => line.trim());
lines.forEach(line => {
const match = line.match(pattern);
if (match) {
const [_, ip, timestamp, hour, method, path, referer] = match;
// IP 주소별 카운트
results.ipCounts[ip] = (results.ipCounts[ip] || 0) + 1;
// 시간대별 카운트
results.hourCounts[hour] = (results.hourCounts[hour] || 0) + 1;
// 서비스별 카운트
const service = extractService(referer);
results.serviceCounts[service] = (results.serviceCounts[service] || 0) + 1;
}
});
return results;
}
function printResults(results) {
console.log("=== IP 주소별 접속 횟수 ===");
Object.entries(results.ipCounts)
.sort(([,a], [,b]) => b - a)
.forEach(([ip, count]) => {
console.log(`${ip}: ${count}회`);
});
console.log("\n=== 시간대별 접속 통계 ===");
Object.entries(results.hourCounts)
.sort(([a], [b]) => Number(a) - Number(b))
.forEach(([hour, count]) => {
console.log(`${hour}시: ${count}회`);
});
console.log("\n=== 서비스별 접속 통계 TOP 5 ===");
Object.entries(results.serviceCounts)
.sort(([,a], [,b]) => b - a)
.slice(0, 5)
.forEach(([service, count]) => {
console.log(`${service}: ${count}회`);
});
}
// 사용 예시
const results = analyzeLogs(data);
printResults(results);
2.2 Python 구현
import re
from datetime import datetime
# 테스트할 한 줄의 로그
log_line = '32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"'
# 정규표현식 패턴
pattern = r'(\d+\.\d+\.\d+\.\d+).*\[(\d+/\w+/\d+:\d+:\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"'
# 매칭 수행
m = re.search(pattern, log_line)
# 변수 할당 및 결과 출력
if m:
ip, timestamp, method, path, referer = m.groups()
print('전체 매칭:', m.group(0))
print('IP 주소 (group 1):', ip)
print('타임스탬프 (group 2):', timestamp)
print('HTTP 메서드 (group 3):', method)
print('경로 (group 4):', path)
print('리퍼러 (group 5):', referer)
# 시간 추출 테스트
try:
dt = datetime.strptime(timestamp, '%d/%b/%Y:%H:%M:%S')
print('\n시간 파싱 결과:')
print('시간:', dt.hour)
print('전체 datetime:', dt)
except ValueError as e:
print('시간 파싱 실패:', e)
else:
print('매칭 실패!')
import re
from datetime import datetime
# 테스트할 한 줄의 로그
log_line = '32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"'
# 정규표현식 패턴
pattern = r'(\d+\.\d+\.\d+\.\d+).*\[(\d+/\w+/\d+:\d+:\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"'
# 매칭 수행
m = re.search(pattern, log_line)
# 변수 할당 및 결과 출력
if m:
ip, timestamp, method, path, referer = m.groups()
print('전체 매칭:', m.group(0))
print('IP 주소 (group 1):', ip)
print('타임스탬프 (group 2):', timestamp)
print('HTTP 메서드 (group 3):', method)
print('경로 (group 4):', path)
print('리퍼러 (group 5):', referer)
# 시간 추출 테스트
try:
dt = datetime.strptime(timestamp, '%d/%b/%Y:%H:%M:%S')
print('\n시간 파싱 결과:')
print('시간:', dt.hour)
print('전체 datetime:', dt)
except ValueError as e:
print('시간 파싱 실패:', e)
else:
print('매칭 실패!')
import re
from collections import Counter
from datetime import datetime
data = '''32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"
187.234.121.55 - - [29/Nov/2024:08:16:12 +0900] "GET /course/python HTTP/1.1" 200 3267 "https://books.weniv.co.kr/" "Mozilla/5.0"
91.45.234.178 - - [29/Nov/2024:08:17:01 +0900] "POST /shorten HTTP/1.1" 201 342 "https://weniv.link/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:18:23 +0900] "GET /schedule HTTP/1.1" 200 2891 "https://time.weniv.co.kr/" "Safari/605.1.15"
211.89.145.67 - - [29/Nov/2024:08:19:45 +0900] "GET / HTTP/1.1" 200 1523 "https://weniv.co.kr" "Firefox/121.0"
167.89.234.90 - - [29/Nov/2024:08:20:11 +0900] "GET /dashboard HTTP/1.1" 200 5632 "https://canvas.weniv.co.kr/" "Edge/120.0.0.0"
45.178.234.89 - - [29/Nov/2024:08:21:34 +0900] "POST /query HTTP/1.1" 200 892 "https://sql.weniv.co.kr/editor" "Chrome/120.0.0.0"
123.45.167.89 - - [29/Nov/2024:08:22:56 +0900] "GET /book/python-basics HTTP/1.1" 200 4521 "https://books.weniv.co.kr/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:23:12 +0900] "GET /stats HTTP/1.1" 200 2341 "https://weniv.link/dashboard" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:24:45 +0900] "GET /calendar/2024 HTTP/1.1" 200 3421 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
178.90.234.156 - - [29/Nov/2024:08:25:23 +0900] "POST /contact HTTP/1.1" 200 567 "https://weniv.co.kr/about" "Edge/120.0.0.0"
90.234.156.78 - - [29/Nov/2024:08:26:34 +0900] "GET /assignments HTTP/1.1" 200 4532 "https://canvas.weniv.co.kr/" "Firefox/121.0"
145.67.89.234 - - [29/Nov/2024:08:27:56 +0900] "GET /practice HTTP/1.1" 200 3421 "https://sql.weniv.co.kr/learn" "Chrome/120.0.0.0"
67.89.234.156 - - [29/Nov/2024:08:28:12 +0900] "GET /ebook/javascript HTTP/1.1" 200 5632 "https://books.weniv.co.kr/" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:29:45 +0900] "POST /shorten/custom HTTP/1.1" 201 445 "https://weniv.link/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:30:23 +0900] "GET /reminder HTTP/1.1" 200 2341 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:31:34 +0900] "GET /courses HTTP/1.1" 200 6789 "https://weniv.co.kr/learning" "Edge/120.0.0.0"
78.90.234.156 - - [29/Nov/2024:08:32:56 +0900] "POST /submit HTTP/1.1" 200 892 "https://canvas.weniv.co.kr/" "Safari/605.1.15"
90.234.156.78 - - [29/Nov/2024:08:33:12 +0900] "GET /advanced HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/courses" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:34:45 +0900] "GET /shop HTTP/1.1" 200 3421 "https://books.weniv.co.kr/" "Chrome/120.0.0.0"
'''
def analyze_logs(log_data):
# 정규표현식 패턴 - Referer 정보 포함
pattern = r'(\d+\.\d+\.\d+\.\d+).*\[(\d+/\w+/\d+:\d+:\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"'
# 결과를 저장할 딕셔너리
results = {
'ip_counts': Counter(),
'hour_counts': Counter(),
'service_counts': Counter()
}
def extract_service(referer):
if not referer or referer == '-':
return 'direct'
service_patterns = {
r'sql\.weniv\.co\.kr': 'SQL 연습장',
r'books\.weniv\.co\.kr': '위니브 책방',
r'weniv\.link': '위니브 링크',
r'time\.weniv\.co\.kr': '위니브 시간표',
r'canvas\.weniv\.co\.kr': '위니브 캔버스',
r'weniv\.co\.kr': '위니브 메인'
}
for pattern, service in service_patterns.items():
if re.search(pattern, referer):
return service
return 'other'
# 각 로그 라인 분석
for line in log_data.split('\n'):
if not line.strip():
continue
m = re.search(pattern, line)
if m:
ip, timestamp, method, path, referer = m.groups()
# IP 주소별 카운트
results['ip_counts'][ip] += 1
# 시간대별 카운트
try:
dt = datetime.strptime(timestamp, '%d/%b/%Y:%H:%M:%S')
results['hour_counts'][dt.hour] += 1
except ValueError:
pass
# 서비스별 카운트
service = extract_service(referer)
results['service_counts'][service] += 1
return results
def print_results(results):
print("=== IP 주소별 접속 횟수 ===")
for ip, count in results['ip_counts'].most_common():
print(f"{ip}: {count}회")
print("\n=== 시간대별 접속 통계 ===")
for hour in sorted(results['hour_counts'].keys()):
print(f"{hour}시: {results['hour_counts'][hour]}회")
print("\n=== 서비스별 접속 통계 TOP 5 ===")
for service, count in results['service_counts'].most_common(5):
print(f"{service}: {count}회")
# 사용 예시
results = analyze_logs(data)
print_results(results)
import re
from collections import Counter
from datetime import datetime
data = '''32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"
187.234.121.55 - - [29/Nov/2024:08:16:12 +0900] "GET /course/python HTTP/1.1" 200 3267 "https://books.weniv.co.kr/" "Mozilla/5.0"
91.45.234.178 - - [29/Nov/2024:08:17:01 +0900] "POST /shorten HTTP/1.1" 201 342 "https://weniv.link/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:18:23 +0900] "GET /schedule HTTP/1.1" 200 2891 "https://time.weniv.co.kr/" "Safari/605.1.15"
211.89.145.67 - - [29/Nov/2024:08:19:45 +0900] "GET / HTTP/1.1" 200 1523 "https://weniv.co.kr" "Firefox/121.0"
167.89.234.90 - - [29/Nov/2024:08:20:11 +0900] "GET /dashboard HTTP/1.1" 200 5632 "https://canvas.weniv.co.kr/" "Edge/120.0.0.0"
45.178.234.89 - - [29/Nov/2024:08:21:34 +0900] "POST /query HTTP/1.1" 200 892 "https://sql.weniv.co.kr/editor" "Chrome/120.0.0.0"
123.45.167.89 - - [29/Nov/2024:08:22:56 +0900] "GET /book/python-basics HTTP/1.1" 200 4521 "https://books.weniv.co.kr/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:23:12 +0900] "GET /stats HTTP/1.1" 200 2341 "https://weniv.link/dashboard" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:24:45 +0900] "GET /calendar/2024 HTTP/1.1" 200 3421 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
178.90.234.156 - - [29/Nov/2024:08:25:23 +0900] "POST /contact HTTP/1.1" 200 567 "https://weniv.co.kr/about" "Edge/120.0.0.0"
90.234.156.78 - - [29/Nov/2024:08:26:34 +0900] "GET /assignments HTTP/1.1" 200 4532 "https://canvas.weniv.co.kr/" "Firefox/121.0"
145.67.89.234 - - [29/Nov/2024:08:27:56 +0900] "GET /practice HTTP/1.1" 200 3421 "https://sql.weniv.co.kr/learn" "Chrome/120.0.0.0"
67.89.234.156 - - [29/Nov/2024:08:28:12 +0900] "GET /ebook/javascript HTTP/1.1" 200 5632 "https://books.weniv.co.kr/" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:29:45 +0900] "POST /shorten/custom HTTP/1.1" 201 445 "https://weniv.link/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:30:23 +0900] "GET /reminder HTTP/1.1" 200 2341 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:31:34 +0900] "GET /courses HTTP/1.1" 200 6789 "https://weniv.co.kr/learning" "Edge/120.0.0.0"
78.90.234.156 - - [29/Nov/2024:08:32:56 +0900] "POST /submit HTTP/1.1" 200 892 "https://canvas.weniv.co.kr/" "Safari/605.1.15"
90.234.156.78 - - [29/Nov/2024:08:33:12 +0900] "GET /advanced HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/courses" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:34:45 +0900] "GET /shop HTTP/1.1" 200 3421 "https://books.weniv.co.kr/" "Chrome/120.0.0.0"
'''
def analyze_logs(log_data):
# 정규표현식 패턴 - Referer 정보 포함
pattern = r'(\d+\.\d+\.\d+\.\d+).*\[(\d+/\w+/\d+:\d+:\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"'
# 결과를 저장할 딕셔너리
results = {
'ip_counts': Counter(),
'hour_counts': Counter(),
'service_counts': Counter()
}
def extract_service(referer):
if not referer or referer == '-':
return 'direct'
service_patterns = {
r'sql\.weniv\.co\.kr': 'SQL 연습장',
r'books\.weniv\.co\.kr': '위니브 책방',
r'weniv\.link': '위니브 링크',
r'time\.weniv\.co\.kr': '위니브 시간표',
r'canvas\.weniv\.co\.kr': '위니브 캔버스',
r'weniv\.co\.kr': '위니브 메인'
}
for pattern, service in service_patterns.items():
if re.search(pattern, referer):
return service
return 'other'
# 각 로그 라인 분석
for line in log_data.split('\n'):
if not line.strip():
continue
m = re.search(pattern, line)
if m:
ip, timestamp, method, path, referer = m.groups()
# IP 주소별 카운트
results['ip_counts'][ip] += 1
# 시간대별 카운트
try:
dt = datetime.strptime(timestamp, '%d/%b/%Y:%H:%M:%S')
results['hour_counts'][dt.hour] += 1
except ValueError:
pass
# 서비스별 카운트
service = extract_service(referer)
results['service_counts'][service] += 1
return results
def print_results(results):
print("=== IP 주소별 접속 횟수 ===")
for ip, count in results['ip_counts'].most_common():
print(f"{ip}: {count}회")
print("\n=== 시간대별 접속 통계 ===")
for hour in sorted(results['hour_counts'].keys()):
print(f"{hour}시: {results['hour_counts'][hour]}회")
print("\n=== 서비스별 접속 통계 TOP 5 ===")
for service, count in results['service_counts'].most_common(5):
print(f"{service}: {count}회")
# 사용 예시
results = analyze_logs(data)
print_results(results)