django-log-formatter-asim 0.0.6__py3-none-any.whl → 1.1.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,10 @@ import json
2
2
  import logging
3
3
  from datetime import datetime
4
4
  from importlib.metadata import distribution
5
+ import os
5
6
 
7
+ import ddtrace
8
+ from ddtrace.trace import tracer
6
9
  from django.conf import settings
7
10
 
8
11
 
@@ -25,6 +28,46 @@ class ASIMRootFormatter:
25
28
 
26
29
  return copied_dict
27
30
 
31
+ def _get_container_id(self):
32
+ """
33
+ The dockerId (container Id) is available via the metadata endpoint. However, it looks like it is embedded in the
34
+ metadata URL e.g.:
35
+ ECS_CONTAINER_METADATA_URI=http://169.254.170.2/v3/709d1c10779d47b2a84db9eef2ebd041-0265927825
36
+ See: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-metadata-endpoint-v4-response.html
37
+ """
38
+
39
+ try:
40
+ return os.environ["ECS_CONTAINER_METADATA_URI"].split("/")[-1]
41
+ except (KeyError, IndexError):
42
+ return ""
43
+
44
+ def _get_first_64_bits_of(self, trace_id):
45
+ # See https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/python/#no-standard-library-logging
46
+ return str((1 << 64) - 1 & trace_id)
47
+
48
+ def _datadog_trace_dict(self):
49
+ event_dict = {}
50
+
51
+ span = tracer.current_span()
52
+ trace_id, span_id = (
53
+ (self._get_first_64_bits_of(span.trace_id), span.span_id)
54
+ if span
55
+ else (None, None)
56
+ )
57
+
58
+ # add ids to structlog event dictionary
59
+ event_dict["dd.trace_id"] = str(trace_id or 0)
60
+ event_dict["dd.span_id"] = str(span_id or 0)
61
+
62
+ # add the env, service, and version configured for the tracer
63
+ event_dict["env"] = ddtrace.config.env or ""
64
+ event_dict["service"] = ddtrace.config.service or ""
65
+ event_dict["version"] = ddtrace.config.version or ""
66
+
67
+ event_dict["container_id"] = self._get_container_id()
68
+
69
+ return event_dict
70
+
28
71
  def get_log_dict(self):
29
72
  record = self.record
30
73
  log_time = datetime.utcfromtimestamp(record.created).isoformat()
@@ -48,6 +91,8 @@ class ASIMRootFormatter:
48
91
  },
49
92
  }
50
93
 
94
+ log_dict.update(self._datadog_trace_dict())
95
+
51
96
  if getattr(settings, "DLFA_INCLUDE_RAW_LOG", False):
52
97
  return self.get_log_dict_with_raw(log_dict)
53
98
 
@@ -0,0 +1,204 @@
1
+ import datetime
2
+ import json
3
+ import sys
4
+ from enum import Enum
5
+ from typing import Literal
6
+ from typing import Optional
7
+ from typing import TypedDict
8
+
9
+
10
+ class AuthenticationType(str, Enum):
11
+ Logon = "Logon"
12
+ Logoff = "Logoff"
13
+
14
+
15
+ class AuthenticationResult(str, Enum):
16
+ Success = "Success"
17
+ Partial = "Partial"
18
+ Failure = "Failure"
19
+ NA = "NA"
20
+
21
+
22
+ class AuthenticationLoginMethod(str, Enum):
23
+ UsernamePassword = "Username & Password"
24
+ StaffSSO = "Staff-SSO"
25
+ UKGOVSSO = "UK.GOV-SSO"
26
+ ExternalIDP = "External IdP"
27
+
28
+
29
+ class Severity(str, Enum):
30
+ Informational = "Informational"
31
+ Low = "Low"
32
+ Medium = "Medium"
33
+ High = "High"
34
+
35
+
36
+ class AuthenticationServer(TypedDict):
37
+ """Dictionary to represent properties of the HTTP Server."""
38
+
39
+ """
40
+ A unique identifier for the server which serviced the Authentication event.
41
+
42
+ Defaults to the WSGI SERVER_NAME field if not provided.
43
+ """
44
+ hostname: Optional[str]
45
+ """Internet Protocol Address of the server serving this request."""
46
+ ipAddr: Optional[str]
47
+
48
+
49
+ class AuthenticationClient(TypedDict):
50
+ """Dictionary to represent properties of the HTTP Client."""
51
+
52
+ """Internet Protocol Address of the client making the Authentication
53
+ event."""
54
+ ipAddr: Optional[str]
55
+
56
+
57
+ class AuthenticationUser(TypedDict):
58
+ """Dictionary to represent properties of the users session."""
59
+
60
+ """What type of role best describes this Authentication event."""
61
+ role: Optional[
62
+ Literal[
63
+ "Regular",
64
+ "Machine",
65
+ "Admin",
66
+ "System",
67
+ "Application",
68
+ "Service Principal",
69
+ "Service",
70
+ "Anonymous",
71
+ "Other",
72
+ ]
73
+ ]
74
+ """
75
+ A unique identifier for the user.
76
+
77
+ Defaults to the logged in Django User.username if not provided.
78
+ """
79
+ username: Optional[str]
80
+ """
81
+ A unique identifier for this authentication session if one exists.
82
+
83
+ Defaults to the Django Sessions session key if not provided.
84
+ """
85
+ sessionId: Optional[str]
86
+
87
+
88
+ def log_authentication(
89
+ request,
90
+ type: AuthenticationType,
91
+ result: AuthenticationResult,
92
+ login_method: AuthenticationLoginMethod,
93
+ user: Optional[AuthenticationUser] = None,
94
+ server: Optional[AuthenticationServer] = None,
95
+ client: Optional[AuthenticationClient] = None,
96
+ severity: Optional[Severity] = None,
97
+ time_generated: Optional[datetime.datetime] = None,
98
+ result_details: Optional[str] = None,
99
+ message: Optional[str] = None,
100
+ ):
101
+ """
102
+ Log an ASIM Authentication Event to standard output.
103
+
104
+ :param request: django.http.HttpRequest object which initiated this Authentication request
105
+ from which the following data will be logged if available
106
+ - Django Authentication systems current username
107
+ - Django Session middlewares Session Key
108
+ - Client IP address
109
+ - Server hostname
110
+ :param type: What authentication action was attempted, either "Logon" or "Logoff"
111
+ :param result: What outcome did the action have, either "Success", "Failure", "Partial", "NA"
112
+ :param login_method: What authentication mechanism was being used, one of:
113
+ - "Username & Password"
114
+ - "Staff-SSO"
115
+ - "UK.GOV-SSO"
116
+ - "External IdP"
117
+ :param user: Dictionary containing information on the subject of this Authentication event
118
+ see AuthenticationUser class for more details.
119
+ :param server: Dictionary containing information on the server servicing this Authentication event
120
+ see AuthenticationServer class for more details.
121
+ :param client: Dictionary containing information on the client performing this Authentication event
122
+ see AuthenticationClient class for more details.
123
+ :param severity: Optional severity of the event, defaults to "Informational", otherwise one of:
124
+ - "Informational"
125
+ - "Low"
126
+ - "Medium"
127
+ - "High"
128
+ :param time_generated: Optional datetime for when the event happened, otherwise datetime.now
129
+ :param result_details: Optional string describing any details associated with the events outcome.
130
+ This field is typically populated when the result is a failure.
131
+ :param message: Optional string describing the reason why the log was generated.
132
+
133
+ See also: https://learn.microsoft.com/en-us/azure/sentinel/normalization-schema-authentication
134
+ """
135
+ if user == None:
136
+ user = {}
137
+ if server == None:
138
+ server = {}
139
+ if client == None:
140
+ client = {}
141
+
142
+ event_created = time_generated or datetime.datetime.now(tz=datetime.timezone.utc)
143
+
144
+ event = {
145
+ "EventCreated": event_created.isoformat(), # TODO: Should this really be EventCreated, or TimeGenerated
146
+ "DvcHostname": server.get("hostname", request.environ.get("SERVER_NAME")),
147
+ "EventSeverity": severity or _default_severity(result),
148
+ "EventOriginalType": _event_code(type, result),
149
+ "SrcIpAddr": client.get("ipAddr", request.environ.get("REMOTE_ADDR")),
150
+ "EventType": type,
151
+ "EventResult": result,
152
+ "LogonMethod": login_method,
153
+ "EventSchema": "Authentication",
154
+ "EventSchemaVersion": "0.1.4",
155
+ }
156
+
157
+ if "role" in user:
158
+ event["ActorUserType"] = user["role"]
159
+
160
+ if "sessionId" in user:
161
+ event["ActorSessionId"] = user["sessionId"]
162
+ elif request.session.session_key:
163
+ event["ActorSessionId"] = request.session.session_key
164
+
165
+ if "username" in user:
166
+ event["ActorUserName"] = user["username"]
167
+ elif request.user.username:
168
+ event["ActorUserName"] = request.user.username
169
+
170
+ if result_details:
171
+ event["EventResultDetails"] = result_details
172
+
173
+ if message:
174
+ event["EventMessage"] = message
175
+
176
+ if "ipAddr" in server:
177
+ event["DvcIpAddr"] = server["ipAddr"]
178
+
179
+ sys.stdout.write(json.dumps(event) + "\n")
180
+ sys.stdout.flush()
181
+
182
+
183
+ log_authentication.Type = AuthenticationType
184
+ log_authentication.Result = AuthenticationResult
185
+ log_authentication.LoginMethod = AuthenticationLoginMethod
186
+ log_authentication.Severity = Severity
187
+
188
+
189
+ def _default_severity(result):
190
+ return Severity.Informational if result == AuthenticationResult.Success else Severity.Medium
191
+
192
+
193
+ def _event_code(type, result):
194
+ if type == AuthenticationType.Logon:
195
+ if result == log_authentication.Result.Success:
196
+ return "001a"
197
+ elif result == AuthenticationResult.Failure:
198
+ return "001b"
199
+ elif type == AuthenticationType.Logoff:
200
+ if result == AuthenticationResult.Success:
201
+ return "001c"
202
+ elif result == AuthenticationResult.Failure:
203
+ return "001d"
204
+ return "001"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: django-log-formatter-asim
3
- Version: 0.0.6
3
+ Version: 1.1.0a0
4
4
  Summary: Formats Django logs in ASIM format.
5
5
  License: MIT
6
6
  Author: Department for Business and Trade Platform Team
@@ -12,9 +12,10 @@ Classifier: Programming Language :: Python :: 3.9
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
- Requires-Dist: django (>=3,<5) ; python_version >= "3.9" and python_version < "3.10"
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Requires-Dist: ddtrace (>=3.2.1,<4.0.0)
17
+ Requires-Dist: django (>=3,<5) ; python_version == "3.9"
16
18
  Requires-Dist: django (>=3,<6) ; python_version >= "3.10" and python_version < "4"
17
- Requires-Dist: pre-commit (>=3.5.0,<4.0.0)
18
19
  Description-Content-Type: text/markdown
19
20
 
20
21
  # Django ASIM log formatter
@@ -0,0 +1,6 @@
1
+ django_log_formatter_asim/__init__.py,sha256=i-7HqYE5s3hjmHLh4TTzLpEdgr5N22msFEQw16Pe_EI,7867
2
+ django_log_formatter_asim/events.py,sha256=fkmT8hxFod-eEviaG34HXu9DwauyiQHoCU3RdFblYrI,6911
3
+ django_log_formatter_asim-1.1.0a0.dist-info/LICENSE,sha256=dP79lN73--7LMApnankTGLqDbImXg8iYFqWgnExGkGk,1090
4
+ django_log_formatter_asim-1.1.0a0.dist-info/METADATA,sha256=CbPwOX4Tn3sWCwyqMr5h8m0nSWjZpl5afT-Ve9ETeXY,5535
5
+ django_log_formatter_asim-1.1.0a0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
6
+ django_log_formatter_asim-1.1.0a0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,5 +0,0 @@
1
- django_log_formatter_asim/__init__.py,sha256=xB7ESmQuHJ1XZJpXOdRIpdB9qra9b_Uc6es3FsgDlEc,6215
2
- django_log_formatter_asim-0.0.6.dist-info/LICENSE,sha256=dP79lN73--7LMApnankTGLqDbImXg8iYFqWgnExGkGk,1090
3
- django_log_formatter_asim-0.0.6.dist-info/METADATA,sha256=ucbxWq0vCqn2UhwBmnfTJfPc1hEJNh0JOYtNBWZ0j5Q,5513
4
- django_log_formatter_asim-0.0.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
5
- django_log_formatter_asim-0.0.6.dist-info/RECORD,,