# 소개
**PyLogger**는 Python 애플리케이션에서 구조화된 로그를 남기고, Fluent Bit 등 외부 로깅 시스템으로 전송할 수 있도록 도와주는 로깅 유틸리티입니다.
# 실행
### 환경 변수 설정
**PyLogger**는 프로젝트 .env 파일에서 다음 값을 읽어옵니다. 사용하는 프로젝트의 루트 디렉토리에 반드시 아래 두 환경 변수를 .env 파일에 설정해야 합니다.
- `FLUENTBIT_URL`: 로그를 전송할 Fluent Bit의 URL
- `COMPONENT_NAME`: 로그에 출력할 서비스 이름 (e.g. `dataTransfer` or `noti`)
- `SYSTEM_NAME`: 로그에 출력할 서비스 이름 (e.g. `aps`,`dp`, `common` or `cp`)
# 지원하는 로그 레벨 및 카테고리
### 로그 레벨 (level)
- `debug`
- `info`
- `warn`
- `error`
- `critical`
### 로그 카테고리 (category)
- `request`
- `response`
- `service`
- `outbound`
- `excel`
- `access`
- `query`
# 주요 메서드 설명
- `bind_base_info()`: 컴포넌트와 시스템 정보로 기본 로그 컨텍스트를 초기화합니다.
- `bind_request_properties(request)`: 요청 정보를 바탕으로 로그 컨텍스트에 요청 속성을 추가합니다. 이 메서드를 호출하면 요청 URL, 메서드, 클라이언트 정보, 헤더 (특히 `tenant-id`, `tenant-name`, `project-name`) 등이 로그 속성에 포함됩니다.
- `send_log(level, category, message)`: 지정된 로그 레벨(`level`), 카테고리(`category`), 메시지(`message`)로 로그를 남기고, 설정된 `FLUENTBIT_URL`로 로그 데이터를 전송합니다.
- `add_to_log(data: dict)`: 추가적인 로그 속성을 현재 로그 컨텍스트에 바인딩합니다. `data`는 키-값 쌍으로 이루어진 딕셔너리이며, 이후 `send_log`로 남기는 로그에 이 속성들이 함께 포함됩니다.
# 설치
### 필요한 패키지
- `loguru`
- `pydantic-settings`
- `requests`
### 설치 예시
1. `AleatorikUI-UI-Backend-Net` 디렉토리로 이동하세요.
2. `Poetry` 패키지 매니저 사용 시 프로젝트의 (e.g. DataTransfer, SmartReport) Poetry 가상환경을 활성화시키세요.
3. 다음 명령들을 실행하세요:
```bash
cd pycommon
poetry install --no--root
```
# 기본 사용법
### 1. 인스턴스 가져오기
```python
from pycommon.pylogger.core import logger_instance
```
### 2. 로그 컨텍스트 바인딩
로그를 남기기 전에, 기본 정보와 HTTP 요청 정보를 바인딩해야 합니다. 먼저 `bind_base_info()`로 컴포넌트와 시스템 정보를 초기화한 후, `bind_request_properties(request)`로 요청 관련 속성을 추가합니다.
```python
from fastapi import Request # 예시: FastAPI Request 객체
async def some_endpoint(request: Request):
logger_instance.bind_base_info()
logger_instance.bind_request_properties(request)
# ... 로직 ...
```
`request`는 FastAPI, Flask 등에서 전달되는 HTTP 요청 객체여야 합니다.
### 3. 로그 보내기
로그를 남길 때는 `send_log` 메서드를 사용합니다.
```python
logger_instance.send_log(level="info", category="response", message="사용자가 로그인했습니다.")
```
# 커스텀 속성 추가
추가적인 정보를 로그에 포함하고 싶을 때는 `add_to_log`를 사용할 수 있습니다.
```python
@app.get("/items/{item_id}")
async def read_item(request: Request, item_id: int):
logger_instance.add_to_log({"item_price": 20.5, "user_role": "guest"})
logger_instance.send_log(level="info", category="request", message=f"아이템 {item_id} 조회 요청")
return {"item_id": item_id}
```
# API 미들웨어 연동
**PyLogger**는 FastAPI 애플리케이션과 미들웨어를 통해 API 요청을 로깅하는 데 효과적으로 사용될 수 있습니다. 다음은 FastAPI 애플리케이션에 미들웨어를 적용하여 각 API 요청을 로깅하는 예시입니다.
```python
from fastapi import Request, Response, FastAPI
from starlette.background import BackgroundTask
from urllib.parse import urlparse
from pycommon.pylogger.logger import logger_instance
def log_request(request: Request) -> None:
"""Helper function to log request details with consistent formatting."""
# Message format: [Info] [Access] - <REQUEST PATH>
log_msg = f"{urlparse(str(request.url)).path}"
logger_instance.send_log(level="info", category="access", message=log_msg)
async def process_request(request: Request, call_next):
"""Middleware to log HTTP request and response using pylogger."""
# Bypass logging for health check endpoints
request_path = urlparse(str(request.url)).path
if request_path.rstrip("/").endswith("/health"):
return await call_next(request)
logger_instance.bind_base_info()
logger_instance.bind_request_properties(request)
response = await call_next(request)
request.state.status_code = response.status_code
# Log in the background without delaying response
task = BackgroundTask(log_request, request)
res_body = b"".join([chunk async for chunk in response.body_iterator])
return Response(
content=res_body,
status_code=response.status_code,
headers=dict(response.headers),
media_type=response.media_type,
background=task
)
def setup_logging_middleware(app: FastAPI):
"""Registers logging middleware in the FastAPI app."""
app.middleware("http")(process_request)
```
위 예시에서 `process_request` 미들웨어는 각 요청에 대해 `log_request` 백그라운드 작업을 실행하여 요청 정보를 로깅합니다. `bind_base_info()`와 `bind_request_properties()`를 통해 로그 컨텍스트가 설정되어 로그에 필요한 기본 정보와 요청 정보가 포함됩니다.
Raw data
{
"_id": null,
"home_page": null,
"name": "aleatorik-pycommon",
"maintainer": null,
"docs_url": null,
"requires_python": "<4,>=3.11",
"maintainer_email": null,
"keywords": "logging, utilities, aleatorik, fastapi, structured-logging",
"author": null,
"author_email": "Aleatorik <info@aleatorik.com>",
"download_url": "https://files.pythonhosted.org/packages/4e/53/9ce597e7da90f30763d1f94b04dd1d386470235666f8873c3764ea5cc6b5/aleatorik_pycommon-1.50.1.tar.gz",
"platform": null,
"description": "# \uc18c\uac1c\r\n\r\n**PyLogger**\ub294 Python \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uc5d0\uc11c \uad6c\uc870\ud654\ub41c \ub85c\uadf8\ub97c \ub0a8\uae30\uace0, Fluent Bit \ub4f1 \uc678\ubd80 \ub85c\uae45 \uc2dc\uc2a4\ud15c\uc73c\ub85c \uc804\uc1a1\ud560 \uc218 \uc788\ub3c4\ub85d \ub3c4\uc640\uc8fc\ub294 \ub85c\uae45 \uc720\ud2f8\ub9ac\ud2f0\uc785\ub2c8\ub2e4.\r\n\r\n# \uc2e4\ud589\r\n\r\n### \ud658\uacbd \ubcc0\uc218 \uc124\uc815\r\n\r\n**PyLogger**\ub294 \ud504\ub85c\uc81d\ud2b8 .env \ud30c\uc77c\uc5d0\uc11c \ub2e4\uc74c \uac12\uc744 \uc77d\uc5b4\uc635\ub2c8\ub2e4. \uc0ac\uc6a9\ud558\ub294 \ud504\ub85c\uc81d\ud2b8\uc758 \ub8e8\ud2b8 \ub514\ub809\ud1a0\ub9ac\uc5d0 \ubc18\ub4dc\uc2dc \uc544\ub798 \ub450 \ud658\uacbd \ubcc0\uc218\ub97c .env \ud30c\uc77c\uc5d0 \uc124\uc815\ud574\uc57c \ud569\ub2c8\ub2e4.\r\n- `FLUENTBIT_URL`: \ub85c\uadf8\ub97c \uc804\uc1a1\ud560 Fluent Bit\uc758 URL\r\n- `COMPONENT_NAME`: \ub85c\uadf8\uc5d0 \ucd9c\ub825\ud560 \uc11c\ube44\uc2a4 \uc774\ub984 (e.g. `dataTransfer` or `noti`)\r\n- `SYSTEM_NAME`: \ub85c\uadf8\uc5d0 \ucd9c\ub825\ud560 \uc11c\ube44\uc2a4 \uc774\ub984 (e.g. `aps`,`dp`, `common` or `cp`)\r\n\r\n# \uc9c0\uc6d0\ud558\ub294 \ub85c\uadf8 \ub808\ubca8 \ubc0f \uce74\ud14c\uace0\ub9ac\r\n\r\n### \ub85c\uadf8 \ub808\ubca8 (level)\r\n- `debug`\r\n- `info`\r\n- `warn`\r\n- `error`\r\n- `critical`\r\n\r\n### \ub85c\uadf8 \uce74\ud14c\uace0\ub9ac (category)\r\n- `request`\r\n- `response`\r\n- `service`\r\n- `outbound`\r\n- `excel`\r\n- `access`\r\n- `query`\r\n\r\n# \uc8fc\uc694 \uba54\uc11c\ub4dc \uc124\uba85\r\n\r\n- `bind_base_info()`: \ucef4\ud3ec\ub10c\ud2b8\uc640 \uc2dc\uc2a4\ud15c \uc815\ubcf4\ub85c \uae30\ubcf8 \ub85c\uadf8 \ucee8\ud14d\uc2a4\ud2b8\ub97c \ucd08\uae30\ud654\ud569\ub2c8\ub2e4.\r\n- `bind_request_properties(request)`: \uc694\uccad \uc815\ubcf4\ub97c \ubc14\ud0d5\uc73c\ub85c \ub85c\uadf8 \ucee8\ud14d\uc2a4\ud2b8\uc5d0 \uc694\uccad \uc18d\uc131\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4. \uc774 \uba54\uc11c\ub4dc\ub97c \ud638\ucd9c\ud558\uba74 \uc694\uccad URL, \uba54\uc11c\ub4dc, \ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4, \ud5e4\ub354 (\ud2b9\ud788 `tenant-id`, `tenant-name`, `project-name`) \ub4f1\uc774 \ub85c\uadf8 \uc18d\uc131\uc5d0 \ud3ec\ud568\ub429\ub2c8\ub2e4.\r\n- `send_log(level, category, message)`: \uc9c0\uc815\ub41c \ub85c\uadf8 \ub808\ubca8(`level`), \uce74\ud14c\uace0\ub9ac(`category`), \uba54\uc2dc\uc9c0(`message`)\ub85c \ub85c\uadf8\ub97c \ub0a8\uae30\uace0, \uc124\uc815\ub41c `FLUENTBIT_URL`\ub85c \ub85c\uadf8 \ub370\uc774\ud130\ub97c \uc804\uc1a1\ud569\ub2c8\ub2e4.\r\n- `add_to_log(data: dict)`: \ucd94\uac00\uc801\uc778 \ub85c\uadf8 \uc18d\uc131\uc744 \ud604\uc7ac \ub85c\uadf8 \ucee8\ud14d\uc2a4\ud2b8\uc5d0 \ubc14\uc778\ub529\ud569\ub2c8\ub2e4. `data`\ub294 \ud0a4-\uac12 \uc30d\uc73c\ub85c \uc774\ub8e8\uc5b4\uc9c4 \ub515\uc154\ub108\ub9ac\uc774\uba70, \uc774\ud6c4 `send_log`\ub85c \ub0a8\uae30\ub294 \ub85c\uadf8\uc5d0 \uc774 \uc18d\uc131\ub4e4\uc774 \ud568\uaed8 \ud3ec\ud568\ub429\ub2c8\ub2e4.\r\n\r\n# \uc124\uce58\r\n\r\n### \ud544\uc694\ud55c \ud328\ud0a4\uc9c0\r\n\r\n- `loguru`\r\n- `pydantic-settings`\r\n- `requests`\r\n\r\n### \uc124\uce58 \uc608\uc2dc\r\n\r\n1. `AleatorikUI-UI-Backend-Net` \ub514\ub809\ud1a0\ub9ac\ub85c \uc774\ub3d9\ud558\uc138\uc694.\r\n2. `Poetry` \ud328\ud0a4\uc9c0 \ub9e4\ub2c8\uc800 \uc0ac\uc6a9 \uc2dc \ud504\ub85c\uc81d\ud2b8\uc758 (e.g. DataTransfer, SmartReport) Poetry \uac00\uc0c1\ud658\uacbd\uc744 \ud65c\uc131\ud654\uc2dc\ud0a4\uc138\uc694.\r\n3. \ub2e4\uc74c \uba85\ub839\ub4e4\uc744 \uc2e4\ud589\ud558\uc138\uc694:\r\n\r\n ```bash\r\n cd pycommon\r\n poetry install --no--root\r\n ```\r\n\r\n\r\n# \uae30\ubcf8 \uc0ac\uc6a9\ubc95\r\n\r\n### 1. \uc778\uc2a4\ud134\uc2a4 \uac00\uc838\uc624\uae30\r\n\r\n```python\r\nfrom pycommon.pylogger.core import logger_instance\r\n```\r\n\r\n### 2. \ub85c\uadf8 \ucee8\ud14d\uc2a4\ud2b8 \ubc14\uc778\ub529\r\n\r\n\ub85c\uadf8\ub97c \ub0a8\uae30\uae30 \uc804\uc5d0, \uae30\ubcf8 \uc815\ubcf4\uc640 HTTP \uc694\uccad \uc815\ubcf4\ub97c \ubc14\uc778\ub529\ud574\uc57c \ud569\ub2c8\ub2e4. \uba3c\uc800 `bind_base_info()`\ub85c \ucef4\ud3ec\ub10c\ud2b8\uc640 \uc2dc\uc2a4\ud15c \uc815\ubcf4\ub97c \ucd08\uae30\ud654\ud55c \ud6c4, `bind_request_properties(request)`\ub85c \uc694\uccad \uad00\ub828 \uc18d\uc131\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4.\r\n\r\n```python\r\nfrom fastapi import Request # \uc608\uc2dc: FastAPI Request \uac1d\uccb4\r\n\r\nasync def some_endpoint(request: Request):\r\n logger_instance.bind_base_info()\r\n logger_instance.bind_request_properties(request)\r\n # ... \ub85c\uc9c1 ...\r\n```\r\n\r\n`request`\ub294 FastAPI, Flask \ub4f1\uc5d0\uc11c \uc804\ub2ec\ub418\ub294 HTTP \uc694\uccad \uac1d\uccb4\uc5ec\uc57c \ud569\ub2c8\ub2e4.\r\n\r\n### 3. \ub85c\uadf8 \ubcf4\ub0b4\uae30\r\n\r\n\ub85c\uadf8\ub97c \ub0a8\uae38 \ub54c\ub294 `send_log` \uba54\uc11c\ub4dc\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4.\r\n\r\n```python\r\nlogger_instance.send_log(level=\"info\", category=\"response\", message=\"\uc0ac\uc6a9\uc790\uac00 \ub85c\uadf8\uc778\ud588\uc2b5\ub2c8\ub2e4.\")\r\n```\r\n\r\n# \ucee4\uc2a4\ud140 \uc18d\uc131 \ucd94\uac00\r\n\r\n\ucd94\uac00\uc801\uc778 \uc815\ubcf4\ub97c \ub85c\uadf8\uc5d0 \ud3ec\ud568\ud558\uace0 \uc2f6\uc744 \ub54c\ub294 `add_to_log`\ub97c \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\r\n\r\n```python\r\n@app.get(\"/items/{item_id}\")\r\nasync def read_item(request: Request, item_id: int):\r\n logger_instance.add_to_log({\"item_price\": 20.5, \"user_role\": \"guest\"})\r\n logger_instance.send_log(level=\"info\", category=\"request\", message=f\"\uc544\uc774\ud15c {item_id} \uc870\ud68c \uc694\uccad\")\r\n return {\"item_id\": item_id}\r\n```\r\n\r\n# API \ubbf8\ub4e4\uc6e8\uc5b4 \uc5f0\ub3d9\r\n\r\n**PyLogger**\ub294 FastAPI \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uacfc \ubbf8\ub4e4\uc6e8\uc5b4\ub97c \ud1b5\ud574 API \uc694\uccad\uc744 \ub85c\uae45\ud558\ub294 \ub370 \ud6a8\uacfc\uc801\uc73c\ub85c \uc0ac\uc6a9\ub420 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ub2e4\uc74c\uc740 FastAPI \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uc5d0 \ubbf8\ub4e4\uc6e8\uc5b4\ub97c \uc801\uc6a9\ud558\uc5ec \uac01 API \uc694\uccad\uc744 \ub85c\uae45\ud558\ub294 \uc608\uc2dc\uc785\ub2c8\ub2e4.\r\n\r\n```python\r\nfrom fastapi import Request, Response, FastAPI\r\nfrom starlette.background import BackgroundTask\r\nfrom urllib.parse import urlparse\r\nfrom pycommon.pylogger.logger import logger_instance\r\n\r\n\r\ndef log_request(request: Request) -> None:\r\n \"\"\"Helper function to log request details with consistent formatting.\"\"\"\r\n # Message format: [Info] [Access] - <REQUEST PATH>\r\n log_msg = f\"{urlparse(str(request.url)).path}\"\r\n logger_instance.send_log(level=\"info\", category=\"access\", message=log_msg)\r\n\r\n\r\nasync def process_request(request: Request, call_next):\r\n \"\"\"Middleware to log HTTP request and response using pylogger.\"\"\"\r\n # Bypass logging for health check endpoints\r\n request_path = urlparse(str(request.url)).path\r\n if request_path.rstrip(\"/\").endswith(\"/health\"):\r\n return await call_next(request)\r\n\r\n logger_instance.bind_base_info()\r\n logger_instance.bind_request_properties(request)\r\n response = await call_next(request)\r\n request.state.status_code = response.status_code\r\n\r\n # Log in the background without delaying response\r\n task = BackgroundTask(log_request, request)\r\n\r\n res_body = b\"\".join([chunk async for chunk in response.body_iterator])\r\n return Response(\r\n content=res_body,\r\n status_code=response.status_code,\r\n headers=dict(response.headers),\r\n media_type=response.media_type,\r\n background=task\r\n )\r\n\r\n\r\ndef setup_logging_middleware(app: FastAPI):\r\n \"\"\"Registers logging middleware in the FastAPI app.\"\"\"\r\n app.middleware(\"http\")(process_request)\r\n```\r\n\r\n\uc704 \uc608\uc2dc\uc5d0\uc11c `process_request` \ubbf8\ub4e4\uc6e8\uc5b4\ub294 \uac01 \uc694\uccad\uc5d0 \ub300\ud574 `log_request` \ubc31\uadf8\ub77c\uc6b4\ub4dc \uc791\uc5c5\uc744 \uc2e4\ud589\ud558\uc5ec \uc694\uccad \uc815\ubcf4\ub97c \ub85c\uae45\ud569\ub2c8\ub2e4. `bind_base_info()`\uc640 `bind_request_properties()`\ub97c \ud1b5\ud574 \ub85c\uadf8 \ucee8\ud14d\uc2a4\ud2b8\uac00 \uc124\uc815\ub418\uc5b4 \ub85c\uadf8\uc5d0 \ud544\uc694\ud55c \uae30\ubcf8 \uc815\ubcf4\uc640 \uc694\uccad \uc815\ubcf4\uac00 \ud3ec\ud568\ub429\ub2c8\ub2e4.\r\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Aleatorik PyCommon - \uad6c\uc870\ud654\ub41c \ub85c\uae45 \ubc0f \uacf5\ud1b5 \uc720\ud2f8\ub9ac\ud2f0 \ub77c\uc774\ube0c\ub7ec\ub9ac",
"version": "1.50.1",
"project_urls": null,
"split_keywords": [
"logging",
" utilities",
" aleatorik",
" fastapi",
" structured-logging"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "0174e2914f739e37bcb5cb4835003c2d848addb2e6888c2d340380184a37ccb0",
"md5": "e99d8203ce4abc9f9d875cf133085549",
"sha256": "aae74aa48362ae64bc2547764f5342de739df1ca4d3604b072ae476e56e0fdda"
},
"downloads": -1,
"filename": "aleatorik_pycommon-1.50.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "e99d8203ce4abc9f9d875cf133085549",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4,>=3.11",
"size": 9909,
"upload_time": "2025-08-05T05:26:24",
"upload_time_iso_8601": "2025-08-05T05:26:24.702571Z",
"url": "https://files.pythonhosted.org/packages/01/74/e2914f739e37bcb5cb4835003c2d848addb2e6888c2d340380184a37ccb0/aleatorik_pycommon-1.50.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "4e539ce597e7da90f30763d1f94b04dd1d386470235666f8873c3764ea5cc6b5",
"md5": "22ba57f18bb9c4f380b600469a9d5389",
"sha256": "f955adf86144f944db44df5d8be92e1b08043807c56aac65dfc16ac691d0514a"
},
"downloads": -1,
"filename": "aleatorik_pycommon-1.50.1.tar.gz",
"has_sig": false,
"md5_digest": "22ba57f18bb9c4f380b600469a9d5389",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4,>=3.11",
"size": 9732,
"upload_time": "2025-08-05T05:26:26",
"upload_time_iso_8601": "2025-08-05T05:26:26.609671Z",
"url": "https://files.pythonhosted.org/packages/4e/53/9ce597e7da90f30763d1f94b04dd1d386470235666f8873c3764ea5cc6b5/aleatorik_pycommon-1.50.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-05 05:26:26",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "aleatorik-pycommon"
}