Skip to content

Commit 97d3ac3

Browse files
authored
feat(repo): Add logging, examples (#17)
* feat(repo): Add logging, examples * fix(containerfile): No dev * fix(pre-commit-config): Remove * fix(template): Add default workflows * fix(branch_ci): Show some options * fix(workflows): Better naming * fix(ci): test folder * fix(tests): Add test * fix(fix): fix
1 parent e6a64ef commit 97d3ac3

File tree

8 files changed

+151
-21
lines changed

8 files changed

+151
-21
lines changed

.github/workflows/branch_ci.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Non-Default Branch Push CI (Python)
2+
3+
on:
4+
push:
5+
branches-ignore: [ "main" ]
6+
paths-ignore: ['README.md']
7+
8+
jobs:
9+
branch-ci:
10+
uses: openclimatefix/.github/.github/workflows/nondefault_branch_push_ci_python.yml@main
11+
secrets: inherit
12+
with:
13+
containerfile: Containerfile
14+
enable_linting: true
15+
enable_typechecking: true
16+
tests_folder: tests
17+

.github/workflows/merged_ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: Default Branch PR Merged CI
2+
3+
on:
4+
pull_request:
5+
types: ["closed"]
6+
branches: [ "main" ]
7+
8+
jobs:
9+
merged-ci:
10+
uses: openclimatefix/.github/.github/workflows/default_branch_pr_merged_ci.yml@main
11+
secrets: inherit

.pre-commit-config.yaml

Lines changed: 0 additions & 20 deletions
This file was deleted.

Containerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM python:3.12-slim-bookworm
2+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
3+
4+
# Add repository code
5+
WORKDIR /opt/app
6+
COPY src /opt/app/src
7+
COPY pyproject.toml /opt/app
8+
COPY .git /opt/app/.git
9+
10+
RUN uv sync --no-dev
11+
12+
ENTRYPOINT ["uv", "run", "--no-dev"]
13+
CMD ["ocf-template-cli"]
14+

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ classifiers = [
2020
"License :: OSI Approved :: MIT License",
2121
]
2222
dependencies = [
23+
"loguru >= 0.7.3",
2324
"numpy >= 1.23.2",
2425
]
2526

@@ -39,6 +40,7 @@ dev = [
3940

4041
[project.scripts]
4142
# Put entrypoints in here
43+
ocf-template-cli = "ocf_template.main:main"
4244

4345
[project.urls]
4446
repository = "https://github.com/openclimatefix/ocf-python-template"

src/ocf_template/__init__.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,53 @@
1-
"""Source File"""
1+
"""Setup logging configuration for the application."""
2+
3+
import json
4+
import sys
5+
import os
6+
import loguru
7+
8+
def development_formatter(record: "loguru.Record") -> str:
9+
"""Format a log record for development."""
10+
return "".join((
11+
"<green>{time:HH:mm:ss.SSS}</green> ",
12+
"<level>{level:<7s}</level> [{file}:{line}] | <level>{message}</level> ",
13+
"<green>{extra}</green>" if record["extra"] else "",
14+
"\n{exception}",
15+
))
16+
17+
def structured_formatter(record: "loguru.Record") -> str:
18+
"""Format a log record as a structured JSON object."""
19+
record["extra"]["serialized"] = json.dumps({
20+
"timestamp": record["time"].strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
21+
"severity": record["level"].name,
22+
"message": record["message"],
23+
"elapsed": record["elapsed"].total_seconds(),
24+
"logging.googleapis.com/labels": {
25+
"python_logger": record["name"],
26+
},
27+
"logging.googleapis.com/sourceLocation": {
28+
"file": record["file"].name,
29+
"line": record["line"],
30+
"function": record["function"],
31+
},
32+
} | record["extra"])
33+
return "{extra[serialized]}\n"
34+
35+
# Define the logging formatter, removing the default one
36+
loguru.logger.remove(0)
37+
if sys.stdout.isatty():
38+
# Simple logging for development
39+
loguru.logger.add(
40+
sys.stdout, format=development_formatter, diagnose=True,
41+
level=os.getenv("LOGLEVEL", "DEBUG"), backtrace=True, colorize=True,
42+
)
43+
else:
44+
# JSON logging for containers
45+
loguru.logger.add(
46+
sys.stdout, format=structured_formatter,
47+
level=os.getenv("LOGLEVEL", "INFO").upper(),
48+
)
49+
50+
# Uncomment and change the list to quieten external libraries
51+
# for logger in ["aiobotocore", "cfgrib"]:
52+
# logging.getLogger(logger).setLevel(logging.WARNING)
53+

src/ocf_template/main.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Entrypoint to the application."""
2+
3+
from importlib.metadata import PackageNotFoundError, version
4+
5+
from loguru import logger as log
6+
7+
try:
8+
__version__ = version(__package__)
9+
except PackageNotFoundError:
10+
__version__ = "v?"
11+
12+
def main() -> None:
13+
"""Entrypoint to the application.
14+
15+
Thanks to the `script` section in `pyproject.toml`, this function is
16+
automatically executed when the package is run as a script via
17+
`ocf-template-cli`. Change name in pyproject to something more suitable
18+
for your package!
19+
"""
20+
log.info("Hello, world!")
21+
log.info("Adding extra context to this log to help with debugging: ", version=__version__)
22+
23+
log.info("You can also contextualize logs in bulk to save having to provide it repeatedly!")
24+
filenames: list[str] = [f"file_{i}.txt" for i in range(5)]
25+
log.debug("Might be some spooky numbers in these files!", num_files=len(filenames))
26+
for filename in filenames:
27+
with log.contextualize(filename=filename):
28+
log.debug("Trying to find spooky numbers in file {filename}")
29+
if "4" in filename:
30+
log.warning("Spooky number found!")
31+
else:
32+
log.debug("No spooky numbers found")
33+
34+
log.info("Different log levels are also nicely formatted")
35+
for i, level in enumerate(["DEBUG", "INFO", "WARNING", "ERROR"]):
36+
log.log(level, f"This is a {level} log message", index=i)
37+
38+
log.info("Consider wrapping your main in a try/except block to handle exceptions gracefully")
39+
try:
40+
y: int = int(3 / 0)
41+
log.warning("This will never be logged", y=y)
42+
except ZeroDivisionError as e:
43+
log.opt(exception=e).error("Caught exception")
44+
45+
log.info("And if you run this in a container, you'll see structured logs instead!")
46+
log.info("Goodbye, world!")
47+

tests/test_something.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import unittest
2+
3+
4+
class TestSomething(unittest.TestCase):
5+
def test_something(self) -> None:
6+
self.assertEqual(1, 1)
7+

0 commit comments

Comments
 (0)