Skip to content

Commit 5f79568

Browse files
Merge pull request #9 from GSS-Cogs/#4-implement-logger
2 parents 00c29ea + b11c30a commit 5f79568

File tree

8 files changed

+630
-6
lines changed

8 files changed

+630
-6
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
.coverage
1+
.coverage
2+
.venv
3+
.DS_Store
4+
*.pyc
5+
.vscode

dpytools/logger/logger.py

Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,157 @@
1+
from typing import Dict, List, Optional, Union
2+
import structlog
3+
import traceback
4+
import json
5+
from datetime import datetime, timezone
16

2-
logger = "I will be the logger"
7+
8+
def level_to_severity(level: int) -> int:
9+
"""
10+
Helper to convert logging level to severity, please
11+
see: https://github.com/ONSdigital/dp-standards/blob/main/LOGGING_STANDARDS.md#severity-levels
12+
"""
13+
if level > 40:
14+
return 0
15+
elif level > 30:
16+
return 1
17+
elif level > 20:
18+
return 2
19+
else:
20+
return 3
21+
22+
23+
def create_error_dict(error: Exception) -> List[Dict]:
24+
"""
25+
Take a python Exception and create a sub dict/document
26+
matching DP logging standards.
27+
"""
28+
29+
# Note: "stack trace" guidance is very go orientated,
30+
# this will be fine for now.
31+
error_dict = {
32+
"message": str(error),
33+
"stack_trace": traceback.format_exc().split("\n"),
34+
}
35+
36+
# Listify in keeping with expected DP logging structures
37+
return [error_dict]
38+
39+
40+
def dp_serializer(event_log, **kw) -> Dict:
41+
"""
42+
Simple serialiser to align structlog defaults
43+
with output expected by:
44+
https://github.com/ONSdigital/dp-standards/blob/main/LOGGING_STANDARDS.md
45+
"""
46+
47+
# Note: literally just avoiding also logging the superfluous top level
48+
# "event" key - we just want its contents
49+
return json.dumps(event_log["event"], **kw)
50+
51+
52+
class DpLogger:
53+
def __init__(self, namespace: str, test_mode: bool = False):
54+
"""
55+
Simple python logger to create structured logs in keeping
56+
with https://github.com/ONSdigital/dp-standards/blob/main/LOGGING_STANDARDS.md
57+
58+
namespace: (required) the namespace for the app in question
59+
test_mode: FOR USAGE DURING TESTING ONLY, makes logging statements return their structured logs.
60+
"""
61+
structlog.configure(
62+
processors=[structlog.processors.JSONRenderer(dp_serializer)]
63+
)
64+
self._logger = structlog.stdlib.get_logger()
65+
self.namespace = namespace
66+
self.test_mode = test_mode
67+
68+
def _log(
69+
self,
70+
event,
71+
level,
72+
error: Optional[List] = None,
73+
data: Optional[Dict] = None,
74+
raw: str = None,
75+
):
76+
log_entry = self._create_log_entry(event, level, data, error, raw)
77+
self._logger.log(level, log_entry)
78+
79+
if self.test_mode:
80+
return log_entry
81+
82+
def _create_log_entry(self, event, level, data, error, raw) -> Dict:
83+
log_entry = {
84+
"created_at": datetime.now(timezone.utc).isoformat(),
85+
"namespace": self.namespace,
86+
"event": event,
87+
"trace_id": "not-implemented",
88+
"span_id": "not-implemented",
89+
"severity": level_to_severity(level),
90+
"data": data if data is not None else {},
91+
}
92+
93+
if error:
94+
log_entry["errors"] = create_error_dict(error)
95+
96+
if raw:
97+
log_entry["raw"] = raw
98+
99+
return log_entry
100+
101+
def debug(self, event: str, raw: str = None, data: Dict = None):
102+
"""
103+
Log at the debug level.
104+
105+
event: the thing that's happened, a simple short english statement
106+
raw : a raw string of any log messages captured for a third party library
107+
data : arbitrary key-value pairs that may be of use in providing context
108+
"""
109+
self._log(event, 10, raw=raw, data=data)
110+
111+
def info(self, event: str, raw: str = None, data: Dict = None):
112+
"""
113+
Log at the info level.
114+
115+
event: the thing that's happened, a simple short english statement
116+
raw : a raw string of any log messages captured for a third party library
117+
data : arbitrary key-value pairs that may be of use in providing context
118+
"""
119+
self._log(event, 20, raw=raw, data=data)
120+
121+
def warning(self, event: str, raw: str = None, data: Dict = None):
122+
"""
123+
Log at the warning level.
124+
125+
event: the thing that's happened, a simple short english statement
126+
raw : a raw string of any log messages captured for a third party library
127+
data : arbitrary key-value pairs that may be of use in providing context
128+
"""
129+
self._log(event, 30, raw=raw, data=data)
130+
131+
def error(self, event: str, error: Exception, raw: str = None, data: Dict = None):
132+
"""
133+
Log at the error level.
134+
135+
event: the thing that's happened, a simple short english statement
136+
error: a caught python Exceotion
137+
raw : a raw string of any log messages captured for a third party library
138+
data : arbitrary key-value pairs that may be of use in providing context
139+
"""
140+
self._log(event, 40, error=error, raw=raw, data=data)
141+
142+
def critical(
143+
self, event: str, error: Exception, raw: str = None, data: Dict = None
144+
):
145+
"""
146+
IMPORTANT: You should only be logging at the critical level during
147+
application failure, i.e if you're app is not in this process of falling
148+
over you should not be logging a critical.
149+
150+
Log at the critical level.
151+
152+
event: the thing that's happened, a simple short english statement
153+
error: a caught python Exceotion
154+
raw : a raw string of any log messages captured for a third party library
155+
data : arbitrary key-value pairs that may be of use in providing context
156+
"""
157+
self._log(event, 50, error=error, raw=raw, data=data)

0 commit comments

Comments
 (0)