본문 바로가기

카테고리 없음

Elasticsearch-dsl, Django 삽질 복기(2)

분석하려는 로그 메시지에 포함된 json 형식의 데이터를 사용하려는 과정에서 두 가지 문제에 부딪혔다.

1. Search API 에서 json 키값을 field로 이용하는 방법.
2. template 단에서 json 키값별로 핸들링하는 방법.

로그 포맷에 대한 표준이 없다보니 로그 데이터의 패턴이 다양한데 그 중 간단한 형태 하나를 예로 보자.

2020-02-03 13:39:14 [INFO ] [OPERATION] - {"logType":"[REMOVE JOB]","mchnId":"T985","mchnTp":"YT","type":"DELETE","jobKey":"MEDU7911824120200203121004"}


이전 글에 적은 적 있지만 Logstash 설정에서 grok 을 이용해서 json 형식의 데이터가 포함되어 있을 경우에는 이를 따로 분리했고 이걸 아래 같은 식으로 ES 매핑 처리했었다.

"mappings" : { 
      "dynamic": false, 
      "properties" : { 
        "@timestamp" : { 
          "type" : "date" 
        }, 
        ... 
      "loglevel" : { "type" : "keyword" }, 
      "message" : { 
        "type" : "text", 
        "fields" : { 
            "keyword" : { 
              "type" : "keyword", 
              "ignore_above" : 256 
            } 
        } 
      }, 
      "jsonmsg" : { 
          "properties" : { 
            "mchnId" : { "type" : "keyword" }, 
            "msgType" : { "type" : "keyword" }, 
            ... 
            "msgJson" : { 
              "properties" : { 
                "connected" : { 
                  "type" : "boolean" 
                }, 
            ... 

} 

이 설정 상태에서 double underbar 형식을 사용해서 테스트해봤을 때는 별 다른 이상 없었다. 

q = Q("match", jsonmsg__msgJson__mchnId='T768')

그런데, 실 개발들어가서 구현하다보니 이것저것 꼬이기 시작했다. double underbar 를 사용하는 것도 제약이 꽤 있었고 그 외에도 다양한 에러에 부딪혔다. 해결 시도 과정에서 다양한 문제들 접했는데 따로 기록해두지 않아 발생 문제들 세세히 정리는 못하겠고 결론으로 곧장 넘어가면 "nested" 를 사용하는 방식으로 해결.

문서들 찾아 읽다보니 Document 매핑 타입 중 nested 오브젝트 처리를 위해 "nested"란 타입이 제공되고 있음을 알았다.

"nested" 사용하기 위해 매핑 설정을 아래와 같이 약간 바꾸었다.

 "mappings" : { 
      ... 
      "jsonmsg" : { 
          "type": "nested", 
          "properties" : { 
              "mchnId" : { "type" : "keyword" }, 
              "msgType" : { "type" : "keyword" }, 
               ... 
               "msgJson" : { 
                   "type": "nested", 
                   "properties" : { 
                       "connected" : { 
                            "type" : "boolean" 
                       }, 
                    ... 
  

이 설정 이용해서 데이터 다시 인덱싱한 후에 "nested" 와 kwargs 이용해서 해결(이 단계에서 model 의 jsonmsg 를 TextField 에서 JSONField 로 수정했는지 다음 단계에서 했는지는 가물가물)

def test_nested(phrase): 
    q = Q('bool', 
          must=[ 
              Q("nested", path="jsonmsg", query=Q("match", **{'jsonmsg.mchnId': phrase})), 
          ], 
          ) 
    s = Search(using=client, index="eep-log") 
    for item in s: 
        print(item.jsonmsg, item["@timestamp"], item.jsonmsg.jobKey) 

jsonmsg 값 결과가 좀 애매하긴 했지만 "nested"로 1번 문제 해결되었다 싶어서 실 애플리케이션에 이 방식 적용 테스트하는데 2번 문제 봉착.
현상은 이랬다.

{'logType': '[REMOVE JOB]', 'type': 'DELETE', 'mchnTp': 'YT'...}

위에 결과처럼 json 값이 60자 까지만 표시되고 나머지는 '...' 로 생략되어 표시된다. 게다가 'jsonmsg.키' 값은 텅 비어있다.
test_nested 에서는 '...' 로 생략 표시되는건 동일했지만 'jsonmsg.키'는 출력되었는데 무엇이 다른가?
search 처리 후 그 결과를 웹 화면까지 전달하는 과정 중 어디에서 문제인걸까?

에러는 안나니 기초 없이 덤벼든 내 입장에서는 더 막막했고 그래서 의심 가는 부분 몇 곳을 일부러 찔러 에러를 발생시켜 보다보니 그제야 차이점이 눈에 들어왔다.

백단의 search 처리 후 프론트와 http 통신하기 위해 serialize 하는 과정에서 뭔가 문제가 있다!

감은 잡았지만 기초 없다니 숱한 헛발질 후에 내린 추정은 jsonmsg 값을 TextField 로 선언해서 처리한게 모든 문제의 원인일거라는 거였다.
이 후의 초보의 삽질은 너무 장황하여 스킵하고 결론으로 직행.

jsonmsg 를 JSONField 로 선언하고 그 값을 to_dict 처리.


models.py : jsonmsg = JSONField 로 필드 타입 변경

class EepLog(models.Model): 
    timestamp = models.DateTimeField() 
    message = TextField() 
    jsonmsg = JSONField(blank=True, null=True, default="") 

 

serializers.py : to_representation 구현하고 dictionary 타입으로 리턴값 변경.

class JSONSerializerField(serializers.Field): 
    def to_internal_value(self, data): 
        return data 

    def to_representation(self, value): 
        return value.to_dict() 

class EeLogSerializer(serializers.ModelSerializer): 
    jsonmsg = JSONSerializerField() 

    class Meta: 
        model = EepLog 
        # filelds = '__all__' 
        fields = ( 
            'message', 
            'jsonmsg', 
            'timestamp', 
        )


이렇게 변경하니 생략 표시되던거랑 'jsonmsg.키'값 비어 있던 문제 다 해결됨.

여기까지가 현재까지의 진행 상황. 

아직 해결하지 못한 중요 과제는 json 메시지 내에 값으로 또다른 json 메시지가 포함되어 있는 경우(이런 nested json 이 여러 단계로 구성되어 있는 경우도 있고)는 핸들링하는 방법 못 찾은거. 가령 'jsonmsg.msgJson.truck.mchnId' 같은 방식은 동작하지 않고 있다. 여러 단계의 nested json 을 각각 dictionary 로 변환해야 하는건가 싶긴한데 아직은 어디서 출발해야 할지 엄두가 안난다.