aleatorik-pycommon


Namealeatorik-pycommon JSON
Version 1.50.1 PyPI version JSON
download
home_pageNone
SummaryAleatorik PyCommon - 구조화된 로깅 및 공통 유틸리티 라이브러리
upload_time2025-08-05 05:26:26
maintainerNone
docs_urlNone
authorNone
requires_python<4,>=3.11
licenseMIT
keywords logging utilities aleatorik fastapi structured-logging
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 소개

**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"
}
        
Elapsed time: 1.94503s