sample.log
// sample.log
10.0.210.17 - - [28/Nov/2022:11:33:28 +0900] "GET /hello HTTP/1.1" 200 615 "-" "curl/7.84.0" "-"
const log = '10.0.210.17 - - [28/Nov/2022:11:33:28 +0900] "GET /hello HTTP/1.1" 200 615 "-" "curl/7.84.0" "-"'
const logPs = log.split(" ");
const ipPs = logPs[0];
const methodPs = logPs[5].split("\"")[1];
const statusPs = logPs[8];
const pathPs = logPs[6];
const regex = /\[(.+)\]/g
const match = regex.exec(raw)[1];
const timestamp = dayjs(match, 'DD/MMM/YYYY:hh:mm:ss +ZZ').toISOString()
console.log(`Source IP: ${ipPs}`);
console.log(`HTTP Method: ${methodPs}`);
console.log(`Status Code: ${statusPs}`);
console.log(`Path: ${pathPs}`);
console.log(`Timestamp: ${timestamp}`);
Source IP: 10.0.210.17
HTTP Method: GET
Status Code: 200
Path: /hello
Timestamp: 28/Nov/2022:11:33:28 +0900
parser.js
#!/usr/bin/env node
const dayjs = require('dayjs')
const customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(customParseFormat)
process.stdin.on("data", data => {
let raw = data.toString()
let parsed = raw.split(" ");
let ipPs = parsed[0];
let methosPs = parsed[5].split("\"")[1];
let statusPs = parsed[8];
let pathPs = parsed[6];
let regex = /\[(.+)\]/g
let match = regex.exec(raw)[1];
let timestamp = dayjs(match, 'DD/MMM/YYYY:hh:mm:ss +ZZ').toISOString()
let source_ip = ipPs;
let method = methosPs;
let status_code = statusPs;
let path = pathPs;
let jsonString = `
{
"source_ip": "${source_ip}",
"method": "${method}",
"status_code": ${status_code},
"path": "${path}",
"timestamp": "${timestamp}"
}`
process.stdout.write(jsonString)
})
$ cat sample.log | ./parser.js
{
"source_ip": "10.0.210.17",
"method": "GET",
"status_code": 200,
"path": "/hello",
"timestamp": "2022-11-28T02:33:28.000Z"
}
.env
HOSTNAME= // 호스트 이름
USERNAMEE= // 아이디
PASSWORD= // 암호
DATABASE= // 데이터베이스 이름
$ ./sql-runner.js < sql/1_reset.sql
DROP TABLE IF EXISTS public.nginx;
CREATE TABLE public.nginx (
id serial4 NOT NULL,
source_ip varchar NULL,
"method" varchar NULL,
status_code varchar NULL,
"path" varchar NULL,
"timestamp" timestamptz NULL,
CONSTRAINT nginx_pk PRIMARY KEY (id)
);
undefined
데이터베이스 연결 닫는 중...
데이터베이스 연결 종료
sql/1_reset.sql
PostgreSQL 데이터베이스의 "공용" 스키마에 "nginx"라는 이름의 새 테이블을 생성하고 이미 존재하는 경우 해당 테이블을 삭제하는 SQL 쿼리문이 적혀있음 "nginx" 테이블에는 각각의 데이터 유형이 있는 6개의 열이 생성됨id
": 직렬 정수(자동 증가)source_ip
": 소스 IP 주소 문자열method
": 요청에 사용된 HTTP 메서드의 문자열status_code
": 서버가 반환한 HTTP 상태 코드 문자열path
": 요청된 URL 경로에 대한 문자열timestamp
": 시간대가 포함된 타임스탬프$ ./sql-runner.js < sql/2_describe_table.sql
SELECT column_name, udt_name, is_nullable FROM information_schema.columns WHERE table_name = 'nginx'
┌─────────┬───────────────┬───────────────┬─────────────┐
│ (index) │ column_name │ udt_name │ is_nullable │
├─────────┼───────────────┼───────────────┼─────────────┤
│ 0 │ 'id' │ 'int4' │ 'NO' │
│ 1 │ 'timestamp' │ 'timestamptz' │ 'YES' │
│ 2 │ 'source_ip' │ 'varchar' │ 'YES' │
│ 3 │ 'method' │ 'varchar' │ 'YES' │
│ 4 │ 'status_code' │ 'varchar' │ 'YES' │
│ 5 │ 'path' │ 'varchar' │ 'YES' │
└─────────┴───────────────┴───────────────┴─────────────┘
데이터베이스 연결 닫는 중...
데이터베이스 연결 종료
sql/2_describe_table.sql
이전 SQL 파일("1_reset.sql")에서 생성된 "nginx" 테이블의 구조에 대한 정보를 얻기 위해 PostgreSQL 데이터베이스를 쿼리 이 SQL 쿼리는 열 이름, 데이터 유형 및 열이 “nginx” 테이블의 모든 열에 대해 NULL 값을 허용하는지 여부를 선택함이는 “information_shema.columns” 시스템 카탈로그를 사용하여 정보를 얻음
출력에는 “nginx” 테이블의 각 열에 대해 하나씩 6개의 행이 있는 테이블과 각 열의 이름, 데이터 유형 및 null 허용 여부를 나타내는 3개의 열이 표시됨
collector.js
#!/usr/bin/env node
const dotenv = require('dotenv')
const { Client } = require('pg')
dotenv.config()
const { HOSTNAME, USERNAMEE, PASSWORD, DATABASE } = process.env
const client = new Client({
host: HOSTNAME,
user: USERNAMEE,
password: PASSWORD,
database: DATABASE
})
client.connect().then(() => {
process.stdin.on("data", async data => {
let raw = data.toString()
let json = JSON.parse(raw)
let queryString = `
INSERT INTO public.nginx (source_ip, method, status_code, path, timestamp)
VALUES (
'${json.source_ip}',
'${json.method}',
'${json.status_code}',
'${json.path}',
'${json.timestamp}'
);
`
console.log(queryString)
try {
await client.query(queryString)
}
catch(e) {
console.log(e)
}
})
}).catch(err => console.log('연결 오류', err.stack))
// Ctrl+C가 입력되면, 데이터베이스를 닫습니다
process.on('SIGINT', async (sig) => {
console.log('\n데이터베이스 연결 닫는 중...')
await client.end()
console.log('데이터베이스 연결 종료')
process.exit(1)
})
cat sample.json | ./collector.js
INSERT INTO public.nginx (source_ip, method, status_code, path, timestamp)
VALUES (
'127.0.0.1',
'POST',
'404',
'/replace-me',
'2022-11-28T02:33:28.000Z'
);
./sql-runner.js < sql/3_display_table_data.sql
SELECT * FROM nginx;
┌─────────┬────┬─────────────┬────────┬─────────────┬───────────────┬──────────────────────────┐
│ (index) │ id │ source_ip │ method │ status_code │ path │ timestamp │
├─────────┼────┼─────────────┼────────┼─────────────┼───────────────┼──────────────────────────┤
│ 0 │ 11 │ '127.0.0.1' │ 'POST' │ '404' │ '/replace-me' │ 2022-11-28T02:33:28.000Z │
└─────────┴────┴─────────────┴────────┴─────────────┴───────────────┴──────────────────────────┘
데이터베이스 연결 닫는 중...
데이터베이스 연결 종료
sql/3_display_table_data.sql
“nginx" 테이블에서 모든 행과 열을 선택하고 결과를 표시하는 쿼리문 쿼리는 별표(*)가 있는 SELECT 문을 와일드카드로 사용하여 모든 열을 선택함 쿼리 결과에는 "id"(기본 키), "source_ip", "method", "status_code", "path" 및 "timestamp"를 포함하여 "nginx" 테이블에 포함된 데이터가 표시됨4_clean_up_table.sql
DELETE FROM public.nginx;
./sql-runner.js < sql/4_clean_up_table.sql
DELETE FROM public.nginx;
┌─────────┐
│ (index) │
├─────────┤
└─────────┘
데이터베이스 연결 닫는 중...
데이터베이스 연결 종료
access.log
tail -f /var/log/nginx/access.log
127.0.0.1 - - [30/Mar/2023:15:02:05 +0900] "GET / HTTP/1.1" 200 412 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
127.0.0.1 - - [30/Mar/2023:15:02:06 +0900] "GET /static/js/main.50329669.js HTTP/1.1" 304 0 "http://localhost:10024/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
127.0.0.1 - - [30/Mar/2023:15:02:06 +0900] "GET /static/css/main.dec85a0c.css HTTP/1.1" 200 815 "http://localhost:10024/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
127.0.0.1 - - [30/Mar/2023:15:02:06 +0900] "GET /logo.svg HTTP/1.1" 200 589 "http://localhost:10024/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
tail -f /var/log/nginx/access.log |./parser.js
tail -f access.log
의 표준 출력(stdout)을 parser.js
의 표준 입력(stdin)이 되도록 파이프 연결{
"source_ip": "127.0.0.1",
"method": "GET",
"status_code": 200,
"path": "/",
"timestamp": "2023-03-30T06:02:05.000Z"
}
tail -f -n 0 /var/log/nginx/access.log |./parser.js | ./collector.js
새로운 출력만 로그로 쌓을 수 있도록 tail -f -n 0
옵션 설정 parser.js
의 표준 출력을 수집기의 표준 입력이 되도록 파이프 연결INSERT INTO public.nginx (source_ip, method, status_code, path, timestamp)
VALUES (
'127.0.0.1','GET','304','/','2023-03-30T06:04:19.000Z'
);
INSERT INTO public.nginx (source_ip, method, status_code, path, timestamp)
VALUES (
'127.0.0.1','GET','304','/','2023-03-30T06:04:24.000Z'
);
INSERT INTO public.nginx (source_ip, method, status_code, path, timestamp)
VALUES (
'127.0.0.1','GET','304','/','2023-03-30T06:04:25.000Z'
);
./sql-runner.js < sql/3_display_table_data.sql
SELECT * FROM nginx;
┌─────────┬────┬─────────────┬────────┬─────────────┬───────────────┬──────────────────────────┐
│ (index) │ id │ source_ip │ method │ status_code │ path │ timestamp │
├─────────┼────┼─────────────┼────────┼─────────────┼───────────────┼──────────────────────────┤
│ 0 │ 1 │ '127.0.0.1' │ 'POST' │ '404' │ '/replace-me' │ 2022-11-28T02:33:28.000Z │
│ 1 │ 2 │ '127.0.0.1' │ 'GET' │ '304' │ '/' │ 2023-03-30T06:04:19.000Z │
│ 2 │ 3 │ '127.0.0.1' │ 'GET' │ '304' │ '/' │ 2023-03-30T06:04:24.000Z │
│ 3 │ 4 │ '127.0.0.1' │ 'GET' │ '304' │ '/' │ 2023-03-30T06:04:25.000Z │
└─────────┴────┴─────────────┴────────┴─────────────┴───────────────┴──────────────────────────┘
데이터베이스 연결 닫는 중...
데이터베이스 연결 종료