Logging

-·2022년 1월 16일
0

강의정리 - MLOps

목록 보기
13/18
post-custom-banner

이제부터는 노강의 온리pdf 자료로 정리한다. 강의영상 제공이 끝났기 때문...
모르는건 검색해보면서 정리한다. 그래도 복사 붙혀넣기는 아니고 타이핑으로 음미하면서 공부하겠습니다.

네이버부스트캠프ai tech 변성윤 강사님의 model serving 강의 pdf파일을 보고 정리한 글입니다. 문제나, 틀린점이 있다면 말씀해주세요 :)

목차

  • Logging Basics
    -- 로그란?
    -- 데이터 적재 방식
    -- 저장된 데이터 활용 방식

  • Logging in Python
    -- Python Logging Module
    -- Logger
    -- Handler
    -- Formatter
    -- Logging Flow

  • Online Serving Logging(BigQuery)
    -- BigQuery 데이터 구조
    -- BigQuery 데이터세트 만들기
    -- BigQuery 테이블 만들기
    -- BigQuery로 실시간 로그 데이터 수집하기

Logging Basics

로그의 어원

  • 통나무
  • 과거 선박의 속도를 측정하기 위해 칩 로그라는 것을 사용
  • 배의 앞에서 통나무를 띄워서 배의 선미까지 도달하는 시간을 재는 방식에 로그를 사용
  • 요즘엔 컴퓨터에 접속한 기록, 특정 행동을 한 경우 남는 것을 로그라고 부름

로그란?

데이터는 이제 우리의 삶 어디에서나 존재

  • 앱을 사용할때마다 우리가 어떤 행동을 하는지 데이터가 남음
  • 이런 데이터를 사용자 로그 데이터, 이벤트 로그 데이터 등으로 부름
  • 위처럼 머신러닝 인퍼런스 요청 로그, 인퍼런스 결과를 저장해야 함.

데이터 적재 방식

데이터의 종류

데이터베이스 데이터(서비스 로그, database에 저장)

  • 서비스가 운영되기 위해 필요한 데이터
    -- 예) 고객이 언제 가입했는지, 어떤 물건을 구입했는지 등

사용자 행동 데이터(유저 행동 로그, 주로 Object Storage, 데이터 웨어하우스에 저장)

  • 유저로그라고 지칭하면 사용자 행동 데이터를 의미
  • 서비스에 반드시 필요한 내용은 아니고, 더 좋은 제품을 만들기 위해 또는 데이터 분석시 필요한 데이터
  • 앱이나 웹에서 유저가 어떤 행동을 하는지를 나타내는 데이터
  • UX와 관련해서 인터랙션이 이루어지는 관점에서 발생하는 데이터
    -- 예) Click, View, 스와이프 등

인프라 데이터(Metric)

  • 백엔드 웹 서버가 제대로 동작하고 있는지 확인하는 데이터
  • Request수, Response 수
  • DB 부하 등

데이터의 종류 (조금더 자세히)

Metric

  • 값을 측정할 때 사용
  • CPU, Memory 등

Log

  • 운영 관점에서 알아야 하는 데이터를 남길 때 사용
  • 함수가 호출되었다. 예외처리가 되었다 등

Trace

  • 개발 관점에서 알아야 하는 것
  • 예외 Trace

저장된 데이터 활용 방식

  1. "image.jpg"로 마스크 분류 모델로 요청했다.
    => image.jpg를 중간에 Object Storage에 저장하면 실제로 우리가 볼 때의 실제 Label과 예측 Label을 파악할 수 있음

  2. "image.jpg"같은 이름의 이미지로 10번 요청했다.
    => 같은 이미지로 예측한다고 하면 중간에 저장해서 기존에 예측한 결과를 바로 Return할 수 있겠다.(Redis등을 사용해 캐싱)

  3. Feature = ((2,5,10,4))으로 수요 예측 모델로 요청했다.
    => 어떤 Feature가 들어오는지 알 수 있고, Feature를 사용할때 모델이 어떻게 예측하는지 알 수 있음.

  4. 현재 시스템이 잘 동작하는지 알 수 있음

데이터가 저장되어 있지 않다면

  • 과거에 어떤 예측을 했는지 알 수 없음
  • print문의 로그를 저장한 곳을 찾아서 확인해야함(Linux서버에 접속하거나..)
  • 모델이 더 발전하기 위한 개선점을 찾기 어려움
  • 현재 시스템이 잘 동작하고 있는지 알기 어려움

데이터 적재 방식

Database(RDB)에 저장하는 방식

  • 다시 웹, 앱 서비스에서 사용되는 경우 활용
  • 실제 서비스용 Database
    Database(NoSQL)에 저장하는 방식
  • Elasticsearch, Logstash or Fluent, Kibana에서 활용하려는 경우
    Object Storage에 저장하는 방식
  • S3, Cloud Storage에 파일 형태로 저장
  • csv, parquet, json 등
  • 별도로 Database나 Data Warehouse로 옮기는 작업이 필요
    Data Warehouse에 저장하는 방식
  • 데이터 분석시 활용하는 데이터 웨어하우스로 바로 저장

RDBMS

  • 관계형 데이터베이스(Realational Database)
  • 행과 열로 구성
  • 데이터의 관계를 정의하고, 데이터 모델링 진행
  • 비즈니스와 연관된 중요한 정보
    -- 예) 고객정보, 주문요청
  • 영구적으로 저장해야하는 것은 데이터베이스에 저장
  • 데이터 추출시 SQL 사용
  • MySQL, PostgreSQL등
  • ERD등 데이터베이스 모델링.

NoSQL

  • 스키마가 Strict한 RDBMS와 다르게 스키마가 없거나 느슨한 스키마만 적용
  • Not Only SQL
  • 데이터가 많아지며 RDbmS로 트래픽을 감당하기 어려워서 개발됨
  • 일반적으로 RDBMS에 비해 쓰기와 읽기 성능이 빠름
  • Key Value Store, Document, Column Family, Graph 등
  • JSON형태와 비슷하며 XML 등도 활용됨
{
 key:value,
 key2:{key2-1:value21, key2-2:value22}
}
  • MongoDB

SQL vs NoSQL

SQL : Relational
NoSQL : Key-Value

Object Storage

  • 어떤 형태의 파일이여도 저장할 수 있는 저장소
  • AWS S3, GCP Cloud Storage등
  • 특정 시스템에서 발생하는 로그를 xxx.log에 저장한 후, Object Storage에 저장하는 형태
  • 비즈니스에서 사용되지 않는 분석을 위한 데이터
  • 이미지, 음성 등을 저장

Data Warehouse

  • 여러 공간에 저장된 데이터를 한 곳으로 저장
  • 데이터창고
  • RDBMS, NoSQL, Object Storage 등에 저장된 데이터를 한 곳으로 옮겨서 처리
  • RDBMS와 같은 SQL을 사용하지만 성능이 더 좋은편
  • AWS Redshift, GCP BigQuery, Snowflake 등

정말 다양한 방법으로 데이터를 저장할 수 있음
상황에 따라 다르지만, 여기선 Serving과정에서 데이터를 기록하기 위함
상황

  • 데이터분석은 BigQuery에서 진행
  • 해당로그는 애플리케이션과 상관없음

사용 가능 대안

  • 파이썬 로깅 모듈을 사용해 CSV로 저장해서 Cloud Storage에 업로드
  • BigQuery로 바로 데이터 추가

Logging in Python

Python Logging Module

파이썬 기본 모듈, logging == 기존 내장 모듈
웹서버, 머신러닝, CLI등 여러 파이썬 코드에서 사용할 수 있음
심각도에 따라 info, devug, error, warning 등 다양한 카테고리로 데이터를 저장할 수 있음

심각도(Severity)

Log Level

LevelValue설명API
DEBUG10(문제해결에 필요한) 자세한 정보를 공유logging.debug()
INFO20작업이 정상적으로 작동하고 있는 경우 사용logging.info()
WARNING30예상하지 못한 일이거나 발생 가능한 문제일 경우, 작업은 정상적으로 수행logging.warning()
ERROR40프로그램이 함수를 실행할 수 없는 심각한 상황logging.error()
CRITICAL50프로그램이 동작할 수 없는 심각한 문제logging.critical()

기본 Logging Level은 WARNING, 설정하지 않으면 WARNING보다 심각한 레벨의 로그만 보여준다.
print대신 이걸 쓰면 체계적으로 로그가 저장됨

logging vs print

  • console에만 output을 출력하는 print
  • logging은 file, websocker 등 파이썬이 다룰 수 있는 모든 포맷으로 output을 출력할 수 있음
    -- 언제 어디서(파일이름과 코드 상의 몇번째 줄인지) 해당 output이 발생했는지 알 수 있음
    -- output을 심각도에 따라 분류할 수 있음
    --- 예)Dev환경에서는 debug로그까지, Prod(운영)환경에서는 info로그만 보기 등)
    -- 다만 print보다 알아야 하는 지식이 존재
print보다 logging을 쓰는 멋진 개발자가 되어보자.
하지만 print가 편할때도 있는걸..

Github Repo Boostcamp-AI-Tech-Product-Serving 참조

#TODO logging.getLogger() Method로 Logger객체 생성
import logging
logger = logging.getLogger("example") # root logger
logger.info("hello world") # 아무런 로그도 출력되지 않는다

Config를 설정하지 않으면, 실행해도 아무런 Output도 출력하지 않음
Console에서 해당 output을 확인하려면 logging config를 지정해야 함

import logging.config

logger_config = {
	'version':`, # required
    'disable_existing_loggers': True, # 다른 Logger를 overriding
    'formatters':{
    	'simpler':{
        	'format':'%(asctime)s|%(levelname)s - %(message)s'
		},
	},
    'loggers':{
    	'example':{
        	'level':'INFO',
            'handlers':['console']
		}
	}
}
logging.config.dictConfig(logger_config)
logger_with_config=logging.getLogger("example")
logger_with_confg.info("이제는 보인다.")

# 출력:
# yyyy-mm-dd hh:mm:ss,sss | INFO - 이제는 보인다.

위에서 지정한 포맷('%(asctime)s|%(levelname)s-%(message)s') 형태로 로그가 출력됨
logging모듈에서 제공하는 정보는 아래 링크에서 확인할 수 있음(asctime, levelname 등)

Logger - Python Logging Component

  • 로그를 생성하는 Method 제공(logger.info()등)
  • 로그 Level과 Logger에 적용된 Filter를 기반으로 처리해야 하는 로그인지 판단
  • Handler에게 LogRecord 인스턴스 전달

logging.getLogger(name)으로 Logger Object 사용

  • name이 주어지면 해당 name의 logger 사용하고, name이 없으면 root logger 사용
  • 마침표로 구분되는 계층 구조
    -- logging.getLogger('foo.bar') => logging.getLogger('foo')의 자식 logger bar 반환

logging.setLevel(): Logger에서 사용할 Level 지정

Handler - Python Logging Component

  • Logger에서 만들어진 Log를 적절한 위치로 전송(파일 저장 또는 Console 출력 등)
  • Level과 Formatter를 각각 설정해서 필터링 할 수 있음
  • StreamHandler, FileHandler, HTTPHandler 등

핸들러는 로그를 적절한 위치로 보내고, 설정과 필터링을 할 수 있다.

import logging
dynamic_logger = logging.getLogger()
log_handler = logging.StreamHandler() # Stream으로 바로 노출하겠다.
formatter = logging.Formatter("%(asctime)s | %(name) | %(levelname)s - %(message)s") 
log_handler.setFormatter(formatter)
dynamic_logger.addHandler(log_handler)

dynamic_logger.info("hello world")

Formatter - Python Logging Component

  • 최종적으로 Log에 출력될 Formatting 설정
  • 시간, Logger 이름, 심각도, Output, 함수 이름, Line 정보, 메시지 등 다양한 정보 제공
  • 마지막으로 우리에게 보여줄 로그 형태

Logging Flow - Python Logging Component

Online Serving Logging(BigQuery)

BigQuery에 Online Serving Input과 Output 로그 적재
1. 빅쿼리 테이블 세팅
2. 빅쿼리에 적재하기 쉽게 JSON형태로 로그를 정제 -> pythonjsonlogger를 사용
3. python logging 모듈을 사용해서 빅쿼레이(실시간)로그 적재(file과 console에도 남을 수 있도록 Handler를 지정)
JSON형태로 저장하는 Logger

import logging
from pythonjsonlogger import jsonlogger

logger = logging.getLogger()
logHandler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter()
logHandler.setFormatter(formatter)
logger.addHandler(logHandler)

logger.info("hello world")

BigQUery

  • Google Cloud Platform의 데이터 웨어하우스
  • 데이터 분석을 위한 도구로 Apache Spark의 대용으로 활용 가능
  • Firebase, Google Analytics 4와 연동되어 많은 회사에서 사용 중

BigQuery 데이터구조

*GCP의 project 내부에 BigQuery의 리소스가 존재

  • Dataset, Table, View 등

BigQuery 데이터세트 만들기

  1. GCP Console에서 BigQuery 접근 - API 사용 - 데이터 세트 만들기 클릭
  2. 적절한 이름의 데이터 세트 생성
  3. 방금 생성한 online_serving_logs데이터셋에서 Create Table 클릭
  4. 파티션 설정: 빅쿼리는 데이터를 조회할 때 모든 데이터를 조회하지 않고 일부를 조회하기 때문에 비용을 줄이기 위한 방법 (빅쿼리는 데이터를 조회할때마다 비용)

BigQuery 테이블 만들기

BigQuery 서비스 계정

  1. 빅쿼리 데이터 로깅을 위해 서비스 계정 생성 - 다른 강의에서 만든 서비스 계정에 BigQuery 권한을 주는 것도 괜찮음
  2. 새 키 만들어서 JSON Key 다운
credentials = service_account.Credentials.from_service_account_file(
	file = credential_json_path # 위에서 발급받은 서비스 계정 JSON파일의 경로 입력
)
self.config= config

self.bigquery_client = bigquery.Client(credentials=self.config.credentials)
#빅쿼리 client를 만들어 query를 실행하고, 테이블 정보를 불러올 수 있음 
self.setLevel(config.level)

BigQuery로 실시간 로그 데이터 수집하기

config.yaml 파일에 formatter에 대한 구성 설정

  • simple : 일반 포맷팅

  • json : json으로 로그를 변환'()'는 logger config에서 사용할 클래스를 지정

  • pythonjsonlogger의 JsonFormatter클래스를 사용하면 일반 텍스틑 json으로 변환

  • handler와 loggers, root logger를 정의

  • console handler는 기본 StreamHandler를 사용해서 콘솔/터미널에 로그를 출력

  • logfile handler는 파일에 로그 저장

  • maxBytes, backupCount 등도 설정 가능

def get_ml_logger(
	config_path: os.PathLike,
    credential_json_path: os.PathLike
    table_ref: bigquery.TableReference, 
    logger_name: str="MLLogger",
) -> logging.Logger:
	"""
    MLLogerfmf 가져온다.
    
    Args:
    	config_path: logger config YAML 파일의 경로
        credential_json_path: service account json 파일 경로
        table_ref: 로그가 저장될 빅쿼리의 table reference(e.g., project.dataset.table_name)
        logger_name:[optional] logger의 이름(default: MLLoger)

	Returns:
    	logging.Logger: MLLogger
	"""
	with open(config_path, "r") as f:
    	logging_config = yaml.safe_load(f)
	logging.config.dictConfig(logging_config)
	_logger = logging.getLogger(logger_name)
    
    #BigQuery Logging Handler 추가합니다. 
    if not credential_json_path:
    	return _logger 
	
    credentials = service_account.Credentials.from_service_account_file(
    	filename=credential_json_path
	)
    bigquery_handler_config = BigqueryHandlerConfig(
    	credentials=credentials,
        table=table_ref,
        formatter = jsonlogger.Jsonformatter(fmt=log_format),
	)
    bigquery_handler = BigqueryHandler(config=bigquery_handler_config)
    _logger.addHandler(bigquery_handler)

class BigqueryHandlerConfig(BaseSettings):
	credentials: service_account.Credentials
    table: Union[str, bigquery.TableReference]
    formatter: logging.Formatter = Field(default_factory= jsonlogger.JsonFormatter)
    level: int = Field(default=logging.INFO)
    
    class Config:
    	arbitrary_types_allowed = True


class BigqueryHandler(StreamHandler):
    def __init__(self, config: BigqueryHandlerConfig) -> None:
        super().__init__()
        self.config = config
        self.bigquery_client = bigquery.Client(credentials=self.config.credentials)
        self.setLevel(config.level)
        self.setFormatter(fmt=self.config.formatter)

    def emit(self, record: LogRecord) -> None:
        message = self.format(record)
        json_message = json.loads(message)
        log_input = BigqueryLogSchema(
            level=json_message["levelname"],
            message=json_message["message"],
            created_at=datetime.fromtimestamp(
                json_message["created"], tz=pytz.timezone("Asia/Seoul")
            ),
        )
        errors = self.bigquery_client.insert_rows_json(
            self.config.table, [json.loads(log_input.json())]
        )
        if errors:
            print(errors)  # 에러가 발생해도 Logging이 정상적으로 동작하게 하기 위해, 별도의 에러 핸들링을 추가하지 않습니다

        

위에서 정의한 logger config를 기반으로 로그를 생성하는 함수

설정은 config.yaml을 load해서, logging.config에 주입

빅쿼리 핸들러 추가
빅쿼리 핸들러에는 BigqueryHandlerConfig라는 클래스로 관련 설정들을 만들고, 주입
윗부분에서 만든 logger에 BigqueryHandler를 추가

BigQueryHandlerConfig 클래스
Pydantic 커스텀 클래스를 사용하기위해 arbitrary_types_allowes = True

emit Method: BigQueryLogSchema로그 형태에 맞게 파싱해서 데이터 가공
bigquery_client.insert_rows_json Method를 사용해 데이터를 실시간으로 저장

위에서 만든 get_ml_logger 함수를 이용해서, 로그를 각각 파일에, console에, 그리고 빅쿼리에 저장

예시코드는 hello world를 10번 출력하도록 설정

Special Mission

  • 앞서 만들었던 프로토타입에 실시간 데이터 적재 파이프라인 추가하기(Input 포함)
  • 만약 이미지 데이터라면 이미지 데이터는 Cloud Storage에 저장하는 과정 추가하기
  • BigQuery학습하기 (참고자료)
profile
-
post-custom-banner

0개의 댓글