1# Copyright 2016 Google LLC 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. 14 15"""Common logging helpers.""" 16 17import logging 18 19from datetime import datetime 20from datetime import timedelta 21from datetime import timezone 22 23import requests 24 25from google.cloud.logging_v2.entries import LogEntry 26from google.cloud.logging_v2.entries import ProtobufEntry 27from google.cloud.logging_v2.entries import StructEntry 28from google.cloud.logging_v2.entries import TextEntry 29 30try: 31 from google.cloud.logging_v2.types import LogSeverity 32except ImportError: # pragma: NO COVER 33 34 class LogSeverity(object): 35 """Map severities for non-GAPIC usage.""" 36 37 DEFAULT = 0 38 DEBUG = 100 39 INFO = 200 40 NOTICE = 300 41 WARNING = 400 42 ERROR = 500 43 CRITICAL = 600 44 ALERT = 700 45 EMERGENCY = 800 46 47 48_NORMALIZED_SEVERITIES = { 49 logging.CRITICAL: LogSeverity.CRITICAL, 50 logging.ERROR: LogSeverity.ERROR, 51 logging.WARNING: LogSeverity.WARNING, 52 logging.INFO: LogSeverity.INFO, 53 logging.DEBUG: LogSeverity.DEBUG, 54 logging.NOTSET: LogSeverity.DEFAULT, 55} 56 57_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f%z" 58"""Time format for timestamps used in API""" 59 60METADATA_URL = "http://metadata.google.internal./computeMetadata/v1/" 61METADATA_HEADERS = {"Metadata-Flavor": "Google"} 62 63 64def entry_from_resource(resource, client, loggers): 65 """Detect correct entry type from resource and instantiate. 66 67 Args: 68 resource (dict): One entry resource from API response. 69 client (~logging_v2.client.Client): 70 Client that owns the log entry. 71 loggers (dict): 72 A mapping of logger fullnames -> loggers. If the logger 73 that owns the entry is not in ``loggers``, the entry 74 will have a newly-created logger. 75 76 Returns: 77 google.cloud.logging_v2.entries._BaseEntry: 78 The entry instance, constructed via the resource 79 """ 80 if "textPayload" in resource: 81 return TextEntry.from_api_repr(resource, client, loggers=loggers) 82 83 if "jsonPayload" in resource: 84 return StructEntry.from_api_repr(resource, client, loggers=loggers) 85 86 if "protoPayload" in resource: 87 return ProtobufEntry.from_api_repr(resource, client, loggers=loggers) 88 89 return LogEntry.from_api_repr(resource, client, loggers=loggers) 90 91 92def retrieve_metadata_server(metadata_key): 93 """Retrieve the metadata key in the metadata server. 94 95 See: https://cloud.google.com/compute/docs/storing-retrieving-metadata 96 97 Args: 98 metadata_key (str): 99 Key of the metadata which will form the url. You can 100 also supply query parameters after the metadata key. 101 e.g. "tags?alt=json" 102 103 Returns: 104 str: The value of the metadata key returned by the metadata server. 105 """ 106 url = METADATA_URL + metadata_key 107 108 try: 109 response = requests.get(url, headers=METADATA_HEADERS) 110 111 if response.status_code == requests.codes.ok: 112 return response.text 113 114 except requests.exceptions.RequestException: 115 # Ignore the exception, connection failed means the attribute does not 116 # exist in the metadata server. 117 pass 118 119 return None 120 121 122def _normalize_severity(stdlib_level): 123 """Normalize a Python stdlib severity to LogSeverity enum. 124 125 Args: 126 stdlib_level (int): 'levelno' from a :class:`logging.LogRecord` 127 128 Returns: 129 int: Corresponding Stackdriver severity. 130 """ 131 return _NORMALIZED_SEVERITIES.get(stdlib_level, stdlib_level) 132 133 134def _add_defaults_to_filter(filter_): 135 """Modify the input filter expression to add sensible defaults. 136 137 Args: 138 filter_ (str): The original filter expression 139 140 Returns: 141 str: sensible default filter string 142 """ 143 144 # By default, requests should only return logs in the last 24 hours 145 yesterday = datetime.now(timezone.utc) - timedelta(days=1) 146 time_filter = f'timestamp>="{yesterday.strftime(_TIME_FORMAT)}"' 147 if filter_ is None: 148 filter_ = time_filter 149 elif "timestamp" not in filter_.lower(): 150 filter_ = f"{filter_} AND {time_filter}" 151 return filter_ 152