로깅을 위한 간단 정규표현식 AtoZ

한승재·2026년 4월 4일
post-thumbnail

정규표현식(Regular Expression; RegExp)는 언뜻 보기에 매우 복잡해 보이지만, 사실 의미를 알고 나면 매우 쉽다!

원본로그: Hello Im SeungJae
정규표현: ^(?<data1>[^ ]*) (?<data2>[^ ]*) (?<data3>[^ ]*)$
캡쳐결과: data1=Hello, data2=Im, data3=SeungJae

위 예시는 간단하게 스페이스로 띄워진 3개의 값(Hello, Im, SeungJae)을 받아온 것이다.

어떻게 가능한건지 글자 하나하나 마다 살펴보자

1. ^$

^ ... $

정규표현식 맨 앞에 있는 ^ 는 시작을 의미하고 맨 뒤에 있는 $ 는 끝을 의미한다.

로깅할 때 의미는 “데이터가 꼭 n개여야 하는가? 아니면 더 있어도 되는가”로 보면 된다.

쉽게 뚜껑이라 생각하면 된다:

여기서는 ^$ 가 둘다 있으므로 data1 , data2, data3 문자열만 있어야 된다는 의미가 된다.

예시 2번처럼 만약 3개가 아니라 더 많이 있다면 로거는 잘못된 로그로 판단하고 그 로그를 무시한다.

만약 앞에 3개만 가져오고 나머지는 무시하게 하고 싶다면 끝문자인 $ 를 없애면 된다

$ 끝문자를 지우면 값이 더 많더라도 잘 가져오는 것을 알 수 있다.

^ 는 로깅을 하는 중에는 거의 건들 일이 없다고 보면 된다.

2. ( , (?)

정규표현식에서 ( ) 는 프로그래밍 언어처럼 아무 의미가 없는게 아니라 값을 캡쳐(긁어오기) 를 할때 쓰는 특수문자이다.

이해를 위해 먼저 ?가 달린 (? 문법부터 알아보자

캡쳐마다 앞에서 부터 1번 2번 3번으로 번호가 정해져있고 각각에 담으라는 말이다.

만약 ? 기호가 없다면 어떻게 될까?

? 기호는 조건에 맞는 딱 하나를 캡쳐하라는 문법인데, 이것이 없으면 조건에 맞으면 전부 캡쳐하라는 문법이 되어 1번 캡쳐가 모든 것을 다 먹어버릴 것이다.

일반적인 로그 환경에선 (? 를 사용해야 한다.

3. <이름>

<이름> 문법은 생각한대로 각 캡쳐에 이름을 달아주는 역할을 한다.

4. [^ ]*

[^문자]* 문법은 캡쳐할 때 어디까지 캡쳐할 것인가로 생각하면 된다.

이때까지 [^ ]* 로 하였는데 이 뜻은 “스페이스가 아닌 것만 캡쳐해 주세요” 이며

쫌 더 풀어 쓰면 “스페이스가 나올때까지만 캡쳐할게요” 라는 의미다.

이처럼 [^"]*" 가 로그에 나올때까지 읽게 할 수 있다.

5. 응용

위 문법들을 응용해서 다음 로그를 파싱해보자.

띄워쓰기로 구분된 간단한 로그

원본로그: 2026/03/12:18:08:00 GET /healthcheck 502
정규표현: ^(?<time>[^ ]*) (?<method>[^ ]*) (?<path>[^ ]*) (?<status_code>[^ ]*)$
캡쳐결과: time=2026/03/12:18:08:00, method=GET, path=/healthcheck, status_code=502

콤마로 구분된 간단한 로그

원본로그: 2026/03/12:18:08:00,GET,/healthcheck,502
정규표현: ^(?<time>[^,]*),(?<method>[^,]*),(?<path>[^,]*),(?<status_code>[^,]*)$
캡쳐결과: time=2026/03/12:18:08:00, method=GET, path=/healthcheck, status_code=502

여러 구분자가 섞여있을 때

원본로그: 2026/03/12:18:08:00 GET,/healthcheck 502
정규표현: ^(?<time>[^ ]*) (?<method>[^,]*),(?<path>[^ ]*) (?<status_code>[^ ])$
캡쳐결과: time=2026/03/12:18:08:00, method=GET, path=/healthcheck, status_code=502

필요없는 값이 있을 때

원본로그: 2026/03/12:18:08:00 hello.com GET /healthcheck 502
정규표현: ^(?<time>[^ ]*) [^ ]* (?<method>[^ ]*) (?<path>[^ ]*) (?<status_code>[^ ])$
캡쳐결과: time=2026/03/12:18:08:00, method=GET, path=/healthcheck, status_code=502

따옴표로 감싸져 있을 때

원본로그: 2026/03/12:18:08:00 "GET /healthcheck" 502
정규표현: ^(?<time>[^ ]*) "(?<method>[^ ]*) (?<path>[^"]*)" (?<status_code>[^ ]*)$
캡쳐결과: time=2026/03/12:18:08:00, method=GET, path=/healthcheck, status_code=502

대괄호로 감싸져 있을 때

원본로그: [2026/03/12:18:08:00] "GET /healthcheck" 502
정규표현: ^\[(?<time>[^\]]*)\] "(?<method>[^ ]*) (?<path>[^"]*)" (?<status_code>[^ ]*)$
캡쳐결과: time=2026/03/12:18:08:00, method=GET, path=/healthcheck, status_code=502

6. 실제 어플리케이션 로그 파싱

원본 로그:
::1 - [2026-03-12T07:59:45Z] "GET /v1/bar HTTP/1.1 200 35.064µs "curl/8.17.0" "

정규 표현식:
^(?<clientip>[^ ]*) - \[(?<timestamp>[^\]]*)\] "(?<method>[^ ]*) (?<path>[^ ]*) (?<protocol>[^ ]*) (?<responsecode>[^ ]*) (?<processingtime>[^ ]*) "(?<useragent>[^"]*)" "$

결과:
clientip=::1
timestamp=2026-03-12T07:59:45Z
method=GET
path=/v1/bar
protocol=HTTP/1.1
responsecode=200
processingtime=35.064µs
useragent=curl/8.17.0

정규 표현식:
^(?:[^ ]*) - \[(?<timestamp>[^\]]*)\] "(?<method>[^ ]*) (?<path>[^ ]*) (?<protocol>[^ ]*) (?<responsecode>[^ ]*) (?<processingtime>[^ ]*) "(?<useragent>[^"]*)" "$

결과:
timestamp=2026-03-12T07:59:45Z
method=GET
path=/v1/bar
protocol=HTTP/1.1
responsecode=200
processingtime=35.064µs
useragent=curl/8.17.0

번외.Time_Format 형식

예시로 2026년 3월 02일 12시 48분 30초라고 들어보자

%Y연도 (4자리)2026
%y연도 (2자리)26
%m월 (숫자)03
%b월 (축약)Mar
%B월 (전체)March
%d일 (2자리)02
%e일 (공백)2
%H시 (24시간)12
%M48
%S30
원본로그: 2026-03-20 04:48:11
날짜포맷: %Y-%m-%d %H:%M:%S
원본로그: 2026/03/20 04:48:11
날짜포맷: %Y/%m/%d %H:%M:%S
원본로그: 20/03/2026 04:48:11
날짜포맷: %d/%m/%Y %H:%M:%S
원본로그: 20/Mar/2026 04:48:11
날짜포맷: %d/%b/%Y %H:%M:%S
원본로그: 2026-03-20T04:48:11Z
날짜포맷: %Y-%m-%dT%H:%M:%SZ
원본로그: 2026-03-12T07:59:45Z
날짜포맷: %Y-%m-%dT%H:%M:%SZ
profile
안녕하세요

0개의 댓글