Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google/cloud/logging_v2/handlers/_helpers.py: 27%
85 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:45 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:45 +0000
1# Copyright 2016 Google LLC All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""Helper functions for logging handlers."""
17import math
18import json
19import re
20import warnings
22try:
23 import flask
24except ImportError: # pragma: NO COVER
25 flask = None
27from google.cloud.logging_v2.handlers.middleware.request import _get_django_request
29_DJANGO_CONTENT_LENGTH = "CONTENT_LENGTH"
30_DJANGO_XCLOUD_TRACE_HEADER = "HTTP_X_CLOUD_TRACE_CONTEXT"
31_DJANGO_TRACEPARENT = "HTTP_TRACEPARENT"
32_DJANGO_USERAGENT_HEADER = "HTTP_USER_AGENT"
33_DJANGO_REMOTE_ADDR_HEADER = "REMOTE_ADDR"
34_DJANGO_REFERER_HEADER = "HTTP_REFERER"
35_FLASK_XCLOUD_TRACE_HEADER = "X_CLOUD_TRACE_CONTEXT"
36_FLASK_TRACEPARENT = "TRACEPARENT"
37_PROTOCOL_HEADER = "SERVER_PROTOCOL"
40def format_stackdriver_json(record, message):
41 """Helper to format a LogRecord in in Stackdriver fluentd format.
43 Returns:
44 str: JSON str to be written to the log file.
46 DEPRECATED: use StructuredLogHandler to write formatted logs to standard out instead.
47 """
48 subsecond, second = math.modf(record.created)
50 payload = {
51 "message": message,
52 "timestamp": {"seconds": int(second), "nanos": int(subsecond * 1e9)},
53 "thread": record.thread,
54 "severity": record.levelname,
55 }
56 warnings.warn(
57 "format_stackdriver_json is deprecated. Use StructuredLogHandler instead.",
58 DeprecationWarning,
59 )
60 return json.dumps(payload, ensure_ascii=False)
63def get_request_data_from_flask():
64 """Get http_request and trace data from flask request headers.
66 Returns:
67 Tuple[Optional[dict], Optional[str], Optional[str], bool]:
68 Data related to the current http request, trace_id, span_id and trace_sampled
69 for the request. All fields will be None if a django request isn't found.
70 """
71 if flask is None or not flask.request:
72 return None, None, None, False
74 # build http_request
75 http_request = {
76 "requestMethod": flask.request.method,
77 "requestUrl": flask.request.url,
78 "userAgent": flask.request.user_agent.string,
79 "protocol": flask.request.environ.get(_PROTOCOL_HEADER),
80 }
82 # find trace id and span id
83 # first check for w3c traceparent header
84 header = flask.request.headers.get(_FLASK_TRACEPARENT)
85 trace_id, span_id, trace_sampled = _parse_trace_parent(header)
86 if trace_id is None:
87 # traceparent not found. look for xcloud_trace_context header
88 header = flask.request.headers.get(_FLASK_XCLOUD_TRACE_HEADER)
89 trace_id, span_id, trace_sampled = _parse_xcloud_trace(header)
91 return http_request, trace_id, span_id, trace_sampled
94def get_request_data_from_django():
95 """Get http_request and trace data from django request headers.
97 Returns:
98 Tuple[Optional[dict], Optional[str], Optional[str], bool]:
99 Data related to the current http request, trace_id, span_id, and trace_sampled
100 for the request. All fields will be None if a django request isn't found.
101 """
102 request = _get_django_request()
104 if request is None:
105 return None, None, None, False
107 # Django can raise django.core.exceptions.DisallowedHost here for a
108 # malformed HTTP_HOST header. But we don't want to import Django modules.
109 try:
110 request_url = request.build_absolute_uri()
111 except Exception:
112 request_url = None
114 # build http_request
115 http_request = {
116 "requestMethod": request.method,
117 "requestUrl": request_url,
118 "userAgent": request.META.get(_DJANGO_USERAGENT_HEADER),
119 "protocol": request.META.get(_PROTOCOL_HEADER),
120 }
122 # find trace id and span id
123 # first check for w3c traceparent header
124 header = request.META.get(_DJANGO_TRACEPARENT)
125 trace_id, span_id, trace_sampled = _parse_trace_parent(header)
126 if trace_id is None:
127 # traceparent not found. look for xcloud_trace_context header
128 header = request.META.get(_DJANGO_XCLOUD_TRACE_HEADER)
129 trace_id, span_id, trace_sampled = _parse_xcloud_trace(header)
131 return http_request, trace_id, span_id, trace_sampled
134def _parse_trace_parent(header):
135 """Given a w3 traceparent header, extract the trace and span ids.
136 For more information see https://www.w3.org/TR/trace-context/
138 Args:
139 header (str): the string extracted from the traceparent header
140 example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
141 Returns:
142 Tuple[Optional[dict], Optional[str], bool]:
143 The trace_id, span_id and trace_sampled extracted from the header
144 Each field will be None if header can't be parsed in expected format.
145 """
146 trace_id = span_id = None
147 trace_sampled = False
148 # see https://www.w3.org/TR/trace-context/ for W3C traceparent format
149 if header:
150 try:
151 VERSION_PART = r"(?!ff)[a-f\d]{2}"
152 TRACE_ID_PART = r"(?![0]{32})[a-f\d]{32}"
153 PARENT_ID_PART = r"(?![0]{16})[a-f\d]{16}"
154 FLAGS_PART = r"[a-f\d]{2}"
155 regex = f"^\\s?({VERSION_PART})-({TRACE_ID_PART})-({PARENT_ID_PART})-({FLAGS_PART})(-.*)?\\s?$"
156 match = re.match(regex, header)
157 trace_id = match.group(2)
158 span_id = match.group(3)
159 # trace-flag component is an 8-bit bit field. Read as an int
160 int_flag = int(match.group(4), 16)
161 # trace sampled is set if the right-most bit in flag component is set
162 trace_sampled = bool(int_flag & 1)
163 except (IndexError, AttributeError):
164 # could not parse header as expected. Return None
165 pass
166 return trace_id, span_id, trace_sampled
169def _parse_xcloud_trace(header):
170 """Given an X_CLOUD_TRACE header, extract the trace and span ids.
172 Args:
173 header (str): the string extracted from the X_CLOUD_TRACE header
174 Returns:
175 Tuple[Optional[dict], Optional[str], bool]:
176 The trace_id, span_id and trace_sampled extracted from the header
177 Each field will be None if not found.
178 """
179 trace_id = span_id = None
180 trace_sampled = False
181 # see https://cloud.google.com/trace/docs/setup for X-Cloud-Trace_Context format
182 if header:
183 try:
184 regex = r"([\w-]+)?(\/?([\w-]+))?(;?o=(\d))?"
185 match = re.match(regex, header)
186 trace_id = match.group(1)
187 span_id = match.group(3)
188 trace_sampled = match.group(5) == "1"
189 except IndexError:
190 pass
191 return trace_id, span_id, trace_sampled
194def get_request_data():
195 """Helper to get http_request and trace data from supported web
196 frameworks (currently supported: Flask and Django).
198 Returns:
199 Tuple[Optional[dict], Optional[str], Optional[str], bool]:
200 Data related to the current http request, trace_id, span_id, and trace_sampled
201 for the request. All fields will be None if a http request isn't found.
202 """
203 checkers = (
204 get_request_data_from_django,
205 get_request_data_from_flask,
206 )
208 for checker in checkers:
209 http_request, trace_id, span_id, trace_sampled = checker()
210 if http_request is not None:
211 return http_request, trace_id, span_id, trace_sampled
213 return None, None, None, False