1
+ from typing import Dict , List , Optional , Union
2
+ import structlog
3
+ import traceback
4
+ import json
5
+ from datetime import datetime , timezone
1
6
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