이제부터는 노강의 온리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로 실시간 로그 데이터 수집하기
로그의 어원
데이터는 이제 우리의 삶 어디에서나 존재
데이터베이스 데이터(서비스 로그, database에 저장)
사용자 행동 데이터(유저 행동 로그, 주로 Object Storage, 데이터 웨어하우스에 저장)
인프라 데이터(Metric)
Metric
Log
Trace
"image.jpg"로 마스크 분류 모델로 요청했다.
=> image.jpg를 중간에 Object Storage에 저장하면 실제로 우리가 볼 때의 실제 Label과 예측 Label을 파악할 수 있음
"image.jpg"같은 이름의 이미지로 10번 요청했다.
=> 같은 이미지로 예측한다고 하면 중간에 저장해서 기존에 예측한 결과를 바로 Return할 수 있겠다.(Redis등을 사용해 캐싱)
Feature = ((2,5,10,4))으로 수요 예측 모델로 요청했다.
=> 어떤 Feature가 들어오는지 알 수 있고, Feature를 사용할때 모델이 어떻게 예측하는지 알 수 있음.
현재 시스템이 잘 동작하는지 알 수 있음
데이터가 저장되어 있지 않다면
Database(RDB)에 저장하는 방식
{
key:value,
key2:{key2-1:value21, key2-2:value22}
}
SQL : Relational
NoSQL : Key-Value
정말 다양한 방법으로 데이터를 저장할 수 있음
상황에 따라 다르지만, 여기선 Serving과정에서 데이터를 기록하기 위함
상황
사용 가능 대안
파이썬 기본 모듈, logging == 기존 내장 모듈
웹서버, 머신러닝, CLI등 여러 파이썬 코드에서 사용할 수 있음
심각도에 따라 info, devug, error, warning 등 다양한 카테고리로 데이터를 저장할 수 있음
심각도(Severity)
Log Level
Level | Value | 설명 | API |
---|---|---|---|
DEBUG | 10 | (문제해결에 필요한) 자세한 정보를 공유 | logging.debug() |
INFO | 20 | 작업이 정상적으로 작동하고 있는 경우 사용 | logging.info() |
WARNING | 30 | 예상하지 못한 일이거나 발생 가능한 문제일 경우, 작업은 정상적으로 수행 | logging.warning() |
ERROR | 40 | 프로그램이 함수를 실행할 수 없는 심각한 상황 | logging.error() |
CRITICAL | 50 | 프로그램이 동작할 수 없는 심각한 문제 | logging.critical() |
기본 Logging Level은 WARNING, 설정하지 않으면 WARNING보다 심각한 레벨의 로그만 보여준다.
print대신 이걸 쓰면 체계적으로 로그가 저장됨
logging vs 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 등)
logging.getLogger(name)으로 Logger Object 사용
logging.setLevel(): Logger에서 사용할 Level 지정
핸들러는 로그를 적절한 위치로 보내고, 설정과 필터링을 할 수 있다.
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")
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")
*GCP의 project 내부에 BigQuery의 리소스가 존재
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)
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번 출력하도록 설정