Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
- uses: pre-commit/action@v3.0.1

- name: Run the automated tests(using pytest)
run: poetry run pytest ./src/test -sv --durations=0
run: poetry run pytest ./tests -sv --durations=0

- uses: pyupio/safety-action@v1
if: runner.os == 'Linux'
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ A template for **Python project**, equipped with best practices, can be used whe
### 🔫 Security check:
- Dependency vulnerability check:[safety](https://github.com/pyupio/safety), support CLI and in CI workflow

### Local log server
TBD

## Guidebook

### Create a reproducible Python development environment with *pyenv* and *Poetry*
Expand Down
4 changes: 2 additions & 2 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: '3'

vars:
TESTDIR: test
TESTDIR: tests

tasks:
linter-watch:
Expand All @@ -28,7 +28,7 @@ tasks:
desc: Run tests(unit, integration and more...) in TESTDIR(default=tests) folder
dir: '{{ .USER_WORKING_DIR }}'
cmds:
- poetry run pytest ./src/{{ .TESTDIR }} -sv --durations=0
- poetry run pytest ./{{ .TESTDIR }} -sv --durations=0

precommit:
desc: Run pre-commit
Expand Down
9 changes: 9 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,12 @@ services:
- ENVIRONMENT=local
- SHELL=/bin/bash
container_name: pyproj-template
depends_on:
- log_server

log_server:
build:
context: ./local_log_server
dockerfile: Dockerfile-log-server
expose:
- "5237/udp"
8 changes: 8 additions & 0 deletions local_log_server/Dockerfile-log-server
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM python:3.10.1-slim

WORKDIR /app

COPY log_server.py /app

CMD ["--port", "5237", "--save"]
ENTRYPOINT ["python", "-u", "log_server.py"]
63 changes: 63 additions & 0 deletions local_log_server/log_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
log_server.py

This module spins up a local UDP log server that can print logs to the console and optionally save them to a text file.
"""

import argparse
import os
import socket
import sys


def parse_arguments(args):
parser = argparse.ArgumentParser(description="Print Logs")
parser.add_argument(
"--save",
dest="save",
action="store_true",
help="Set flag to save logs to text file.",
)
parser.add_argument(
"--no-save",
dest="save",
action="store_false",
help="Set flag not to save logs to text file.",
)
parser.set_defaults(save=True)
parser.add_argument(
"--port",
default=5091,
help="Use flag to specify logger port (default 5091).",
type=int,
)

return parser.parse_args()


def spin_up_log_server(udp_ip, udp_port, save=False):
"""Spin up a local udp log server."""
try:
os.remove("logfile.txt")
except Exception:
pass

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Internet # UDP
sock.bind((udp_ip, udp_port))
while True:
# buffer size is 1024 bytes
data = sock.recvfrom(1024)[0]
print(data.decode("utf-8"))
# write logs to file
if save:
with open("logfile.txt", "a") as log_file:
log_file.write(data.decode("utf-8") + "\n")


if __name__ == "__main__":
args = parse_arguments(sys.argv[1:])

UDP_PORT = int(args.port)
UDP_IP = "0.0.0.0"

spin_up_log_server(UDP_IP, UDP_PORT, args.save)
70 changes: 70 additions & 0 deletions src/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""
logger.py

This module defines loggers with different settings/configurations that can be chosen automatically based on the ENVIRONMENT variable.
"""

import logging
import logging.config
import os
import sys

# Define base log configuration
base_log_config: dict = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"simple_formatter": {
"format": "%(name)-8s %(asctime)s - %(levelname)s - %(message)s",
# Leaving datefmt string empty will result in the following timeformat:
# 2023-08-24 13:45:42,774
# TODO: Explicit formatting would be better such as
# "datefmt": "%Y-%m-%d %H:%M:%S"
# However, with datefmt it's apparently not possible to add milliseconds to
# the time. Thus, using empty string for now.
"datefmt": "",
},
"detailed_formatter": {
"format": " %(name)-8s[%(asctime)s %(filename)s -> %(funcName)s(): line:%(lineno)s] %(levelname)s: %(message)s"
},
},
# Define handlers than can be used in loggers
"handlers": {
"stream_handler": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "simple_formatter",
"stream": sys.stderr,
},
"local_docker_handler": {
"class": "logging.handlers.SysLogHandler",
"level": "DEBUG",
"formatter": "detailed_formatter",
"address": ("log_server", 5237),
},
},
# Under "loggers", every dict is a configuration for a specific logger(key[logger name]-value[config])
"loggers": {
"local_logger": {
"handlers": ["local_docker_handler"],
"level": "DEBUG",
"propagate": True, # If true, this logger would have its own handler and root’s handler
}
},
# Root logger configuration
"root": {
"handlers": ["stream_handler"],
"level": "WARNING",
"propagate": False,
},
}

# Set configuration
logging.config.dictConfig(base_log_config)


# Get logger based on environment
if os.environ.get("ENVIRONMENT") == "local":
logger = logging.getLogger("local_logger")
else:
logger = logging.getLogger()
8 changes: 8 additions & 0 deletions src/test_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import logging

from logger import logger

root_logger = logging.getLogger()

logger.info("test")
root_logger.warning("test")
File renamed without changes.
File renamed without changes.
File renamed without changes.