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