์•ˆ๋…•ํ•˜์„ธ์š” Mambo ์ž…๋‹ˆ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„๋กœ ์š”์ฒญ๋˜๋Š” ์ •๋ณด๋ฅผ ๊ธฐ๋กํ•˜๋Š” ์•ก์„ธ์Šค ๋กœ๊ทธ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์šด์˜๊ณผ ์žฅ์•  ๋Œ€์‘์— ์žˆ์–ด์„œ ์ƒ๋‹นํžˆ ์ค‘์š”ํ•œ ์ •๋ณด์ž…๋‹ˆ๋‹ค. ์•„๋งˆ์กด ์›น ์„œ๋น„์Šค์—์„œ๋„ Amazon S3 ์„œ๋ฒ„ ์•ก์„ธ์Šค ๋กœ๊น… ํ™œ์„ฑํ™”์ฒ˜๋Ÿผ ์•ก์„ธ์Šค ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์ฃ . ์Šคํ”„๋ง ๋ถ€ํŠธ๋Š” ํ†ฐ์บฃ(Tomcat) ์ด๋‚˜ ์–ธ๋”ํ† ์šฐ(Undertow) ์™€ ๊ฐ™์€ WAS์— ๋Œ€ํ•˜์—ฌ ์•ก์„ธ์Šค ๋กœ๊ทธ๋ฅผ ํŒŒ์ผ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ๊ธฐ๋ณธ์ ์œผ๋กœ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋งŒ์•ฝ, ์–ธ๋”ํ† ์šฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์•ก์„ธ์Šค ๋กœ๊ทธ๋ฅผ ํ™œ์„ฑํ™”ํ•˜๊ณ  ํŒจํ„ด์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

server.undertow.accesslog.enabled=true
server.undertow.accesslog.pattern=common

์Šคํ”„๋ง ๋ถ€ํŠธ ๋กœ๊น…

์Šคํ”„๋ง ๋ถ€ํŠธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Slf4j ๊ธฐ๋ฐ˜์˜ ๋กœ๊ทธ๋ฐฑ(Logback)์„ ๋กœ๊น… ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋กœ๊ทธ๋ฅผ ์—˜๋ผ์Šคํ‹ฑ์„œ์น˜์— ๊ธฐ๋กํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Logstash Logback Encoder์™€ ๊ฐ™์ด ๋กœ๊ทธ๋ฐฑ์œผ๋กœ ๊ธฐ๋ก๋˜๋Š” ๋กœ๊ทธ๋ฅผ ์—˜๋ผ์Šคํ‹ฑ์„œ์น˜๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๊ตฌํ˜„ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

Logback Elasticsearch Appender

Logback Elasticsearch Appender๋Š” ELK ์Šคํƒ์ด ์•„๋‹ˆ๋”๋ผ๋„ ๋กœ๊ทธ๋ฐฑ์œผ๋กœ ๊ธฐ๋ก๋˜๋Š” ๋กœ๊ทธ๋ฅผ ์—˜๋ผ์Šคํ‹ฑ์„œ์น˜๋กœ ์ „๋‹ฌํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์˜ค๋Š˜์€ ์ด๊ฒƒ์„ ํ™œ์šฉํ•˜์—ฌ ์Šคํ”„๋ง ๋ถ€ํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์•ก์„ธ์Šค ๋กœ๊ทธ๋ฅผ ์—˜๋ผ์Šคํ‹ฑ์„œ์น˜์— ๊ธฐ๋กํ•ด๋ณด๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์œ„ ์ฒ˜๋Ÿผ Logback Access์— ๋Œ€ํ•œ Appender๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์•ก์„ธ์Šค ๋กœ๊ทธ๋ฅผ ์‰ฝ๊ฒŒ ์—˜๋ผ์Šคํ‹ฑ์„œ์น˜๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Logback Access Spring Boot Starter

logback-access-spring-boot-starter๋Š” ๋กœ๊ทธ๋ฐฑ ์—‘์„ธ์Šค ์„ค์ •์— ๋Œ€ํ•œ ์Šคํ”„๋ง ๋ถ€ํŠธ ์Šคํƒ€ํ„ฐ์ž…๋‹ˆ๋‹ค. ์–ธ๋”ํ† ์šฐ๊นŒ์ง€ ์ง€์›ํ•˜๋ฏ€๋กœ ์–ธ๋”ํ† ์šฐ์— ๋Œ€ํ•ด ๋กœ๊ทธ๋ฐฑ ์—‘์„ธ์Šค ์„ค์ •์„ ์œ„ํ•œ ์ปค์Šคํ„ฐ๋งˆ์ด์ €๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผํ•˜๊ธฐ

์Šคํ”„๋ง ๋ถ€ํŠธ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ณ  ์ €๋Š” ์–ธ๋”ํ† ์šฐ๋ฅผ ์„ ํ˜ธํ•˜๋ฏ€๋กœ ํ†ฐ์บฃ ๋ชจ๋“ˆ์„ ์ œ์™ธํ•˜๊ณ  ์–ธ๋”ํ† ์šฐ ์Šคํƒ€ํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

configurations.all {
    exclude module: 'spring-boot-starter-tomcat'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-undertow'
    implementation 'com.internetitem:logback-elasticsearch-appender:1.6'
    implementation 'dev.akkinoc.spring.boot:logback-access-spring-boot-starter:3.2.1'
}

๋กœ๊ทธ๋ฐฑ ์—‘์„ธ์Šค ์Šคํƒ€ํ„ฐ๋Š” ์–ธ๋”ํ† ์šฐ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ํ†ฐ์บฃ๋„ ์ง€์›ํ•˜๋ฏ€๋กœ ์„ค์ •์— ๋Œ€ํ•œ ์ฐจ์ด๋Š” ์—†์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋กœ๊ทธ๋ฐฑ ์—‘์„ธ์Šค๋ฅผ ์œ„ํ•œ ์„ค์ • ํŒŒ์ผ์„ ํด๋ž˜์ŠคํŒจ์Šค์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์„ค์ • ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์šฐ์„ ์ˆœ์œ„์— ๋”ฐ๋ผ ์„ค์ • ํŒŒ์ผ๋ช…์€ logback-access-spring.xml ์ด๋ผ๊ณ  ์ƒ์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

logback-access-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <springProperty scope="context" name="elasticsearch_uris" source="spring.elasticsearch.uris" defaultValue="http://localhost:9200"/>
    <appender name="ELASTIC" class="com.internetitem.logback.elasticsearch.ElasticsearchAccessAppender">
        <url>${elasticsearch_uris}/_bulk</url>
        <index>application-accesslog-%date{yyyy-MM-dd}</index>
        <headers>
            <header>
                <name>Content-Type</name>
                <value>application/json</value>
            </header>
        </headers>
    </appender>
    <appender-ref ref="ELASTIC"/>
</configuration>

์Šคํ”„๋ง ๋ถ€ํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๊ณ  ๋ธŒ๋ผ์šฐ์ € ๋˜๋Š” ํด๋ผ์ด์–ธํŠธ ๋„๊ตฌ๋ฅผ ํ†ตํ•ด ์•ก์„ธ์Šค ๊ธฐ๋ก์„ ๋‚จ๊ฒจ๋ณด๊ณ  ์—˜๋ผ์Šคํ‹ฑ์„œ์น˜์— ์ž˜ ์ €์žฅ๋˜๋Š”์ง€ ํ™•์ธํ•ด๋ด…๋‹ˆ๋‹ค.

์ธ๋ฑ์Šค๋Š” ์ƒ์„ฑ๋˜์—ˆ์ง€๋งŒ ์•ก์„ธ์Šค ๋กœ๊ทธ์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์•ก์„ธ์Šค ๋กœ๊ทธ ์ •๋ณด๋ฅผ ๊ธฐ๋กํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Logback Access conversion words๋ฅผ ์ฐธ๊ณ ํ•ด์„œ ํ”„๋กœํผํ‹ฐ๋ฅผ ์„ค์ •ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์•ž์„œ ์ƒ์„ฑํ•œ ๋กœ๊ทธ๋ฐฑ ์—‘์„ธ์Šค ์„ค์ • ํŒŒ์ผ์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ”„๋กœํผํ‹ฐ ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€๋กœ ์ •์˜ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <springProperty scope="context" name="elasticsearch_uris" source="spring.elasticsearch.uris" defaultValue="http://localhost:9200"/>
    <appender name="ELASTIC" class="com.internetitem.logback.elasticsearch.ElasticsearchAccessAppender">
        <url>${elasticsearch_uris}/_bulk</url>
        <index>application-accesslog-%date{yyyy-MM-dd}</index>
        <properties>
            <property>
                <name>contentLength</name>
                <value>%b</value>
            </property>
            <property>
                <name>remoteHost</name>
                <value>%h</value>
            </property>
            <property>
                <name>protocol</name>
                <value>%H</value>
            </property>
            <property>
                <name>referer</name>
                <value>%i{Referer}</value>
            </property>
            <property>
                <name>userAgent</name>
                <value>%i{User-Agent}</value>
            </property>
            <property>
                <name>requestMethod</name>
                <value>%m</value>
            </property>
            <property>
                <name>statusCode</name>
                <value>%s</value>
            </property>
            <property>
                <name>elapsedTime</name>
                <value>%D</value>
            </property>
            <property>
                <name>date</name>
                <value>%t{yyyy-MM-dd'T'HH:mm:ss}</value>
            </property>
            <property>
                <name>user</name>
                <value>%u</value>
            </property>
            <property>
                <name>queryString</name>
                <value>%q</value>
            </property>
            <property>
                <name>requestURI</name>
                <value>%U</value>
            </property>
        </properties>
        <headers>
            <header>
                <name>Content-Type</name>
                <value>application/json</value>
            </header>
        </headers>
    </appender>
    <appender-ref ref="ELASTIC"/>
</configuration>

๋ฏธ๋ฆฌ ์ •์˜๋œ ํŒจํ„ด์ธ combined๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ๋ฆฌํผ๋Ÿฌ์™€ ์œ ์ € ์—์ด์ „ํŠธ ํ—ค๋”๋ฅผ ํฌํ•จํ•˜๋„๋ก ํ”„๋กœํผํ‹ฐ๋ฅผ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

์œ„ ์ฒ˜๋Ÿผ ํ”„๋กœํผํ‹ฐ๋ฅผ ์„ค์ •ํ•˜์˜€์œผ๋ฏ€๋กœ ์š”์ฒญ๋œ ์•ก์„ธ์Šค์— ๋Œ€ํ•œ ๋กœ๊ทธ๋ฅผ ํ†ตํ•ด ์œ ์ € ์—์ด์ „ํŠธ๊ฐ€ ํฌ์ŠคํŠธ๋งจ์ด๋ฉฐ ์š”์ฒญ๋œ ๊ฒฝ๋กœ๋Š” ์•ก์ถ”์—์ดํ„ฐ์ธ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผํ•˜์‹œ๋Š” ์—ฌ๋Ÿฌ๋ถ„์€ ๋‹ค์–‘ํ•œ ํ•ญ๋ชฉ์— ๋Œ€ํ•ด์„œ๋„ ๊ธฐ๋กํ•ด๋ณด์‹œ๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์•ก์„ธ์Šค ๋กœ๊ทธ ์ธ๋ฑ์Šค ํ…œํ”Œ๋ฆฟ

์ƒ์„ฑ๋œ ์•ก์„ธ์Šค ๋กœ๊ทธ์— ๋Œ€ํ•œ ์ธ๋ฑ์Šค ๋งคํ•‘ ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๋ฉด ๋™์  ๋งคํ•‘์— ์˜ํ•ด์„œ ์—˜๋ผ์Šคํ‹ฑ์„œ์น˜๊ฐ€ ํ•„๋“œ ์œ ํ˜•์„ ์ž„์˜๋Œ€๋กœ ์ง€์ •ํ•œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋™์  ๋งคํ•‘์€ ์ €์žฅ๋˜๋Š” ๋„ํ๋จผํŠธ์˜ ํ•„๋“œ ์ •๋ณด๋ฅผ ์•Œ๊ธฐ ์–ด๋ ค์šธ ๋•Œ๋Š” ํŽธ๋ฆฌํ•œ ๊ธฐ๋Šฅ์ด์ง€๋งŒ ์ง€๊ธˆ์ฒ˜๋Ÿผ ์•ก์„ธ์Šค ๋กœ๊ทธ์— ์ €์žฅ๋˜๋Š” ํ•„๋“œ๊ฐ€ ๊ณ ์ •๋˜์–ด์žˆ๊ณ  ํ•„๋“œ ์œ ํ˜•์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ์ •์  ๋งคํ•‘์„ ์ •์˜ํ•ด๋‘๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

์•ก์„ธ์Šค ๋กœ๊ทธ์— ๊ธฐ๋ก๋˜๋Š” ์ •๋ณด๊ฐ€ ๋ช…ํ™•ํ•˜๋ฏ€๋กœ ๋™์  ๋งคํ•‘์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๊ณ  ๋ฏธ๋ฆฌ ์ •์˜๋œ ๋งคํ•‘์„ ์‚ฌ์šฉํ•˜๋„๋ก ์ธ๋ฑ์Šค ํ…œํ”Œ๋ฆฟ์„ ์ •์˜ํ•ด๋‘๊ฒ ์Šต๋‹ˆ๋‹ค.

{
  "index_patterns": [
    "applicaiton-accesslog-*"
  ],
  "template": {
    "settings": {
      "analysis": {
        "analyzer": {
          "path_analyzer": {
            "tokenizer": "path_hierarchy"
          }
        }
      }
    },
    "mappings": {
      "properties": {
        "@timestamp": {
          "type": "date"
        },
        "contentLength": {
          "type": "long"
        },
        "date": {
          "type": "date",
          "format": "date_hour_minute_second || epoch_millis"
        },
        "elapsedTime": {
          "type": "integer"
        },
        "protocol": {
          "type": "keyword"
        },
        "referer": {
          "type": "keyword"
        },
        "remoteHost": {
          "type": "ip"
        },
        "requestMethod": {
          "type": "keyword"
        },
        "requestURI": {
          "type": "text",
          "analyzer": "path_analyzer"
        },
        "statusCode": {
          "type": "short"
        },
        "user": {
          "type": "keyword"
        },
        "userAgent": {
          "type": "keyword"
        }
      }
    }
  }
}

ํ˜„์žฌ ์กฐ์ง์—์„œ๋Š” ์—˜๋ผ์Šคํ‹ฑ์„œ์น˜์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋กœ๊ทธ ๋ฐ ์•ก์„ธ์Šค ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•˜์ง€๋Š” ์•Š๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค๋งŒ, ์—˜๋ผ์Šคํ‹ฑ์„œ์น˜๋ฅผ ํ•™์Šตํ•˜๊ธฐ ์œ„ํ•œ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์–ด์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ๋‚˜๋งˆ ์•ก์„ธ์Šค ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•ด๋ณด๋ฉด ์–ด๋– ํ• ๊นŒ ์ƒ๊ฐํ•ด์„œ ์‹œ๋„ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ๋Œ€๋Ÿ‰์˜ ๋žœ๋ค ์•ก์„ธ์Šค ๋กœ๊ทธ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ ์ปฌ๋Ÿผํ˜• ์‹œ๊ณ„์—ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์ธ KDB๋ฅผ ํ™œ์šฉํ•ด๋ณด๊ณ ๋Š” ์žˆ์Šต๋‹ˆ๋‹ค๋งŒ ์‰ฝ์ง€๋Š” ์•Š๋„ค์š”. ์ด ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ๋Š” ๋งŽ์ด ์‹œ๋„ํ•ด๋ณด๊ณ  ์ •๋ฆฌํ•˜์—ฌ ๊ณต์œ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.