Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google/cloud/logging_v2/handlers/_helpers.py: 28%
81 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 07:30 +0000
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 07:30 +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 # build http_request
108 http_request = {
109 "requestMethod": request.method,
110 "requestUrl": request.build_absolute_uri(),
111 "userAgent": request.META.get(_DJANGO_USERAGENT_HEADER),
112 "protocol": request.META.get(_PROTOCOL_HEADER),
113 }
115 # find trace id and span id
116 # first check for w3c traceparent header
117 header = request.META.get(_DJANGO_TRACEPARENT)
118 trace_id, span_id, trace_sampled = _parse_trace_parent(header)
119 if trace_id is None:
120 # traceparent not found. look for xcloud_trace_context header
121 header = request.META.get(_DJANGO_XCLOUD_TRACE_HEADER)
122 trace_id, span_id, trace_sampled = _parse_xcloud_trace(header)
124 return http_request, trace_id, span_id, trace_sampled
127def _parse_trace_parent(header):
128 """Given a w3 traceparent header, extract the trace and span ids.
129 For more information see https://www.w3.org/TR/trace-context/
131 Args:
132 header (str): the string extracted from the traceparent header
133 example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
134 Returns:
135 Tuple[Optional[dict], Optional[str], bool]:
136 The trace_id, span_id and trace_sampled extracted from the header
137 Each field will be None if header can't be parsed in expected format.
138 """
139 trace_id = span_id = None
140 trace_sampled = False
141 # see https://www.w3.org/TR/trace-context/ for W3C traceparent format
142 if header:
143 try:
144 VERSION_PART = r"(?!ff)[a-f\d]{2}"
145 TRACE_ID_PART = r"(?![0]{32})[a-f\d]{32}"
146 PARENT_ID_PART = r"(?![0]{16})[a-f\d]{16}"
147 FLAGS_PART = r"[a-f\d]{2}"
148 regex = f"^\\s?({VERSION_PART})-({TRACE_ID_PART})-({PARENT_ID_PART})-({FLAGS_PART})(-.*)?\\s?$"
149 match = re.match(regex, header)
150 trace_id = match.group(2)
151 span_id = match.group(3)
152 # trace-flag component is an 8-bit bit field. Read as an int
153 int_flag = int(match.group(4), 16)
154 # trace sampled is set if the right-most bit in flag component is set
155 trace_sampled = bool(int_flag & 1)
156 except (IndexError, AttributeError):
157 # could not parse header as expected. Return None
158 pass
159 return trace_id, span_id, trace_sampled
162def _parse_xcloud_trace(header):
163 """Given an X_CLOUD_TRACE header, extract the trace and span ids.
165 Args:
166 header (str): the string extracted from the X_CLOUD_TRACE header
167 Returns:
168 Tuple[Optional[dict], Optional[str], bool]:
169 The trace_id, span_id and trace_sampled extracted from the header
170 Each field will be None if not found.
171 """
172 trace_id = span_id = None
173 trace_sampled = False
174 # see https://cloud.google.com/trace/docs/setup for X-Cloud-Trace_Context format
175 if header:
176 try:
177 regex = r"([\w-]+)?(\/?([\w-]+))?(;?o=(\d))?"
178 match = re.match(regex, header)
179 trace_id = match.group(1)
180 span_id = match.group(3)
181 trace_sampled = match.group(5) == "1"
182 except IndexError:
183 pass
184 return trace_id, span_id, trace_sampled
187def get_request_data():
188 """Helper to get http_request and trace data from supported web
189 frameworks (currently supported: Flask and Django).
191 Returns:
192 Tuple[Optional[dict], Optional[str], Optional[str], bool]:
193 Data related to the current http request, trace_id, span_id, and trace_sampled
194 for the request. All fields will be None if a http request isn't found.
195 """
196 checkers = (
197 get_request_data_from_django,
198 get_request_data_from_flask,
199 )
201 for checker in checkers:
202 http_request, trace_id, span_id, trace_sampled = checker()
203 if http_request is not None:
204 return http_request, trace_id, span_id, trace_sampled
206 return None, None, None, False