Skip to content

Commit 1945182

Browse files
committed
feat: first implementation
1 parent 3333c56 commit 1945182

File tree

9 files changed

+443
-0
lines changed

9 files changed

+443
-0
lines changed

.github/workflows/release.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Semantic Release
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
release:
10+
runs-on: ubuntu-latest
11+
concurrency: release
12+
permissions:
13+
id-token: write
14+
contents: write
15+
16+
steps:
17+
- uses: actions/checkout@v3
18+
with:
19+
fetch-depth: 0
20+
21+
- name: Python Semantic Release
22+
uses: python-semantic-release/python-semantic-release@master
23+
with:
24+
github_token: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/tests.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
on:
2+
push:
3+
branches:
4+
- "**"
5+
tags-ignore:
6+
- "*.*.*"
7+
8+
name: tests
9+
10+
concurrency:
11+
group: tests
12+
13+
jobs:
14+
tests:
15+
strategy:
16+
matrix:
17+
python-version: ["3.10", "3.11", "3.12"]
18+
poetry-version: ["1.8.2"]
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v3
22+
name: Checkout
23+
24+
- uses: actions/setup-python@v4
25+
name: Setup Python
26+
with:
27+
python-version: ${{ matrix.python-version }}
28+
29+
- uses: abatilo/actions-poetry@v3
30+
name: Install Poetry
31+
with:
32+
poetry-version: ${{ matrix.poetry-version }}
33+
34+
- name: Update Poetry cache location
35+
run: poetry config virtualenvs.in-project true
36+
37+
- id: venv_cache
38+
uses: actions/cache@v3
39+
name: Cache or Restore venv
40+
with:
41+
path: .venv
42+
key: venv-${{ matrix.python-version }}-${{ matrix.poetry-version }}-lock-${{ hashFiles('poetry.lock') }}
43+
44+
- name: Install Poetry Dependencies
45+
run: poetry install
46+
if: steps.venv_cache.outputs.cache-hit != 'true'
47+
48+
- name: Run ruff
49+
run: poetry run ruff check .
50+
51+
- name: Run ruff format
52+
run: poetry run ruff format --check .
53+
54+
- name: Run tests
55+
run: poetry run pytest

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Remote Python log formatter
2+
3+
## Sample usage
4+
5+
```python
6+
from remote_log_formatter import get_logger, setup_logging
7+
8+
setup_logging(json=False)
9+
logger = get_logger(__package__)
10+
logger.info("foo")
11+
```

poetry.lock

Lines changed: 127 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[tool.poetry]
2+
name = "remote-log-formatter"
3+
version = "0.1.0"
4+
description = ""
5+
authors = ["Paolo Quadri <[email protected]>"]
6+
readme = "README.md"
7+
8+
[tool.poetry.dependencies]
9+
python = "^3.10"
10+
11+
[tool.poetry.group.dev.dependencies]
12+
ruff = "^0.3.4"
13+
pytest = "^8.1.1"
14+
15+
[build-system]
16+
requires = ["poetry-core"]
17+
build-backend = "poetry.core.masonry.api"

remote_log_formatter/__init__.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import json
2+
import logging
3+
import logging.config
4+
import os
5+
import sys
6+
from datetime import datetime
7+
8+
9+
class SimpleFormatter(logging.Formatter):
10+
def __init__(
11+
self, fmt=None, datefmt=None, style="%", validate=True, *, defaults=None
12+
) -> None:
13+
if fmt is None:
14+
fmt = "%(asctime)s | %(levelname)s | %(message)s | %(pathname)s:%(lineno)s"
15+
super(SimpleFormatter, self).__init__(fmt, datefmt, style, validate)
16+
17+
18+
class JSONFormatter(logging.Formatter):
19+
def __init__(self, *args, **kwargs) -> None:
20+
super(JSONFormatter, self).__init__(*args, **kwargs)
21+
22+
self._pid = os.getpid()
23+
24+
@staticmethod
25+
def format_timestamp(time: float) -> str:
26+
return datetime.fromtimestamp(time).isoformat()
27+
28+
def format(self, record: logging.LogRecord) -> str:
29+
try:
30+
msg = record.msg % record.args
31+
except TypeError:
32+
msg = record.msg
33+
34+
extra = {"type": "log"}
35+
36+
exc = ""
37+
if record.exc_info:
38+
if not record.exc_text:
39+
record.exc_text = self.formatException(record.exc_info)
40+
extra["class"] = str(record.exc_info[0])
41+
42+
if record.exc_text:
43+
if exc[-1:] != "\n":
44+
exc += "\n"
45+
exc += record.exc_text
46+
47+
if record.stack_info:
48+
if exc[-1:] != "\n":
49+
exc += "\n"
50+
exc += self.formatStack(record.stack_info)
51+
52+
if len(exc):
53+
extra["traceback"] = exc
54+
extra["type"] = "exception"
55+
56+
message = {
57+
"datetime": self.format_timestamp(record.created),
58+
"level": record.levelname,
59+
"message": msg,
60+
"channel": record.name,
61+
"pid": record.process,
62+
"context": {
63+
"processname": record.processName,
64+
"pathname": record.pathname,
65+
"module": record.module,
66+
"function": record.funcName,
67+
"lineno": record.lineno,
68+
},
69+
"extra": extra,
70+
}
71+
72+
return json.dumps(message, default=str)
73+
74+
75+
def setup_logging(json: bool = True) -> None:
76+
LOG_CONFIG = dict(
77+
version=1,
78+
disable_existing_loggers=False,
79+
root={
80+
"level": "INFO",
81+
"handlers": ["console"],
82+
},
83+
loggers={
84+
"root": {
85+
"level": "INFO",
86+
"handlers": ["console"],
87+
"propagate": True,
88+
}
89+
},
90+
handlers={
91+
"console": {
92+
"class": "logging.StreamHandler",
93+
"formatter": "generic" if json else "simple",
94+
"stream": sys.stdout,
95+
},
96+
},
97+
formatters={
98+
"generic": {"class": "remote_log_formatter.JSONFormatter"},
99+
"simple": {"class": "remote_log_formatter.SimpleFormatter"},
100+
},
101+
)
102+
103+
logging.config.dictConfig(LOG_CONFIG)
104+
105+
106+
def get_logger(name: str = "remote") -> logging.Logger:
107+
return logging.getLogger(name)

remote_log_formatter/__main__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from remote_log_formatter import get_logger, setup_logging
2+
3+
setup_logging(json=False)
4+
logger = get_logger(__package__)
5+
logger.info("foo")

tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)