|
| 1 | +# Copyright 2016 Google Inc. 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. |
| 14 | + |
| 15 | +"""Client for interacting with the Stackdriver Logging API""" |
| 16 | + |
| 17 | +import traceback |
| 18 | + |
| 19 | +import gcloud.logging.client |
| 20 | + |
| 21 | + |
| 22 | +class HTTPContext(object): |
| 23 | + """HTTPContext defines an object that captures the parameter for the |
| 24 | + httpRequest part of Error Reporting API |
| 25 | +
|
| 26 | + :type method: string |
| 27 | + :param method: The type of HTTP request, such as GET, POST, etc. |
| 28 | +
|
| 29 | + :type url: string |
| 30 | + :param url: The URL of the request |
| 31 | +
|
| 32 | + :type user_agent: string |
| 33 | + :param user_agent: The user agent information that is provided with the |
| 34 | + request. |
| 35 | +
|
| 36 | + :type referrer: string |
| 37 | + :param referrer: The referrer information that is provided with the |
| 38 | + request. |
| 39 | +
|
| 40 | + :type response_status_code: int |
| 41 | + :param response_status_code: The HTTP response status code for the request. |
| 42 | +
|
| 43 | + :type remote_ip: string |
| 44 | + :param remote_ip: The IP address from which the request originated. This |
| 45 | + can be IPv4, IPv6, or a token which is derived from |
| 46 | + the IP address, depending on the data that has been |
| 47 | + provided in the error report. |
| 48 | + """ |
| 49 | + |
| 50 | + def __init__(self, method=None, url=None, |
| 51 | + user_agent=None, referrer=None, |
| 52 | + response_status_code=None, remote_ip=None): |
| 53 | + self.method = method |
| 54 | + self.url = url |
| 55 | + # intentionally camel case for mapping to JSON API expects |
| 56 | + # pylint: disable=invalid-name |
| 57 | + self.userAgent = user_agent |
| 58 | + self.referrer = referrer |
| 59 | + self.responseStatusCode = response_status_code |
| 60 | + self.remoteIp = remote_ip |
| 61 | + |
| 62 | + |
| 63 | +class Client(object): |
| 64 | + """Error Reporting client. Currently Error Reporting is done by creating |
| 65 | + a Logging client. |
| 66 | +
|
| 67 | + :type project: string |
| 68 | + :param project: the project which the client acts on behalf of. If not |
| 69 | + passed falls back to the default inferred from the |
| 70 | + environment. |
| 71 | +
|
| 72 | + :type credentials: :class:`oauth2client.client.OAuth2Credentials` or |
| 73 | + :class:`NoneType` |
| 74 | + :param credentials: The OAuth2 Credentials to use for the connection |
| 75 | + owned by this client. If not passed (and if no ``http`` |
| 76 | + object is passed), falls back to the default inferred |
| 77 | + from the environment. |
| 78 | +
|
| 79 | + :type http: :class:`httplib2.Http` or class that defines ``request()``. |
| 80 | + :param http: An optional HTTP object to make requests. If not passed, an |
| 81 | + ``http`` object is created that is bound to the |
| 82 | + ``credentials`` for the current object. |
| 83 | +
|
| 84 | + :type service: str |
| 85 | + :param service: An identifier of the service, such as the name of the |
| 86 | + executable, job, or Google App Engine service name. This |
| 87 | + field is expected to have a low number of values that are |
| 88 | + relatively stable over time, as opposed to version, |
| 89 | + which can be changed whenever new code is deployed. |
| 90 | +
|
| 91 | +
|
| 92 | + :type version: str |
| 93 | + :param version: Represents the source code version that the developer |
| 94 | + provided, which could represent a version label or a Git |
| 95 | + SHA-1 hash, for example. If the developer did not provide |
| 96 | + a version, the value is set to default. |
| 97 | +
|
| 98 | + :raises: :class:`ValueError` if the project is neither passed in nor |
| 99 | + set in the environment. |
| 100 | + """ |
| 101 | + |
| 102 | + def __init__(self, project=None, |
| 103 | + credentials=None, |
| 104 | + http=None, |
| 105 | + service=None, |
| 106 | + version=None): |
| 107 | + self.logging_client = gcloud.logging.client.Client( |
| 108 | + project, credentials, http) |
| 109 | + self.service = service if service else self.DEFAULT_SERVICE |
| 110 | + self.version = version |
| 111 | + |
| 112 | + DEFAULT_SERVICE = 'python' |
| 113 | + |
| 114 | + def _send_error_report(self, message, |
| 115 | + report_location=None, http_context=None, user=None): |
| 116 | + """Makes the call to the Error Reporting API via the log stream. |
| 117 | +
|
| 118 | + This is the lower-level interface to build the payload, generally |
| 119 | + users will use either report() or report_exception() to automatically |
| 120 | + gather the parameters for this method. |
| 121 | +
|
| 122 | + Currently this method sends the Error Report by formatting a structured |
| 123 | + log message according to |
| 124 | +
|
| 125 | + https://cloud.google.com/error-reporting/docs/formatting-error-messages |
| 126 | +
|
| 127 | + :type message: string |
| 128 | + :param message: The stack trace that was reported or logged by the |
| 129 | + service. |
| 130 | +
|
| 131 | + :type report_location: dict |
| 132 | + :param report_location: The location in the source code where the |
| 133 | + decision was made to report the error, usually the place |
| 134 | + where it was logged. For a logged exception this would be the |
| 135 | + source line where the exception is logged, usually close to |
| 136 | + the place where it was caught. |
| 137 | +
|
| 138 | + This should be a Python dict that contains the keys 'filePath', |
| 139 | + 'lineNumber', and 'functionName' |
| 140 | +
|
| 141 | + :type http_context: :class`gcloud.error_reporting.HTTPContext` |
| 142 | + :param http_context: The HTTP request which was processed when the |
| 143 | + error was triggered. |
| 144 | +
|
| 145 | + :type user: string |
| 146 | + :param user: The user who caused or was affected by the crash. This can |
| 147 | + be a user ID, an email address, or an arbitrary token that |
| 148 | + uniquely identifies the user. When sending an error |
| 149 | + report, leave this field empty if the user was not |
| 150 | + logged in. In this case the Error Reporting system will |
| 151 | + use other data, such as remote IP address, |
| 152 | + to distinguish affected users. |
| 153 | + """ |
| 154 | + payload = { |
| 155 | + 'serviceContext': { |
| 156 | + 'service': self.service, |
| 157 | + }, |
| 158 | + 'message': '{0}'.format(message) |
| 159 | + } |
| 160 | + |
| 161 | + if self.version: |
| 162 | + payload['serviceContext']['version'] = self.version |
| 163 | + |
| 164 | + if report_location or http_context or user: |
| 165 | + payload['context'] = {} |
| 166 | + |
| 167 | + if report_location: |
| 168 | + payload['context']['reportLocation'] = report_location |
| 169 | + |
| 170 | + if http_context: |
| 171 | + http_context_dict = http_context.__dict__ |
| 172 | + # strip out None values |
| 173 | + # once py26 support is dropped this can use dict comprehension |
| 174 | + payload['context']['httpContext'] = dict( |
| 175 | + (k, v) for (k, v) in http_context_dict.iteritems() |
| 176 | + if v is not None |
| 177 | + ) |
| 178 | + |
| 179 | + if user: |
| 180 | + payload['context']['user'] = user |
| 181 | + |
| 182 | + logger = self.logging_client.logger('errors') |
| 183 | + logger.log_struct(payload) |
| 184 | + |
| 185 | + def report(self, message, http_context=None, user=None): |
| 186 | + """ Reports a message to Stackdriver Error Reporting |
| 187 | + https://cloud.google.com/error-reporting/docs/formatting-error-messages |
| 188 | +
|
| 189 | + :type message: str |
| 190 | + :param message: A user-supplied message to report |
| 191 | +
|
| 192 | +
|
| 193 | + :type http_context: :class`gcloud.error_reporting.HTTPContext` |
| 194 | + :param http_context: The HTTP request which was processed when the |
| 195 | + error was triggered. |
| 196 | +
|
| 197 | + :type user: string |
| 198 | + :param user: The user who caused or was affected by the crash. This |
| 199 | + can be a user ID, an email address, or an arbitrary |
| 200 | + token that uniquely identifies the user. When sending |
| 201 | + an error report, leave this field empty if the user |
| 202 | + was not logged in. In this case the Error Reporting |
| 203 | + system will use other data, such as remote IP address, |
| 204 | + to distinguish affected users. |
| 205 | +
|
| 206 | + Example:: |
| 207 | + >>> client.report("Something went wrong!") |
| 208 | + """ |
| 209 | + stack = traceback.extract_stack() |
| 210 | + last_call = stack[-2] |
| 211 | + file_path = last_call[0] |
| 212 | + line_number = last_call[1] |
| 213 | + function_name = last_call[2] |
| 214 | + report_location = { |
| 215 | + 'filePath': file_path, |
| 216 | + 'lineNumber': line_number, |
| 217 | + 'functionName': function_name |
| 218 | + } |
| 219 | + |
| 220 | + self._send_error_report(message, |
| 221 | + http_context=http_context, |
| 222 | + user=user, |
| 223 | + report_location=report_location) |
| 224 | + |
| 225 | + def report_exception(self, http_context=None, user=None): |
| 226 | + """ Reports the details of the latest exceptions to Stackdriver Error |
| 227 | + Reporting. |
| 228 | +
|
| 229 | + :type http_context: :class`gcloud.error_reporting.HTTPContext` |
| 230 | + :param http_context: The HTTP request which was processed when the |
| 231 | + error was triggered. |
| 232 | +
|
| 233 | + :type user: string |
| 234 | + :param user: The user who caused or was affected by the crash. This |
| 235 | + can be a user ID, an email address, or an arbitrary |
| 236 | + token that uniquely identifies the user. When sending an |
| 237 | + error report, leave this field empty if the user was |
| 238 | + not logged in. In this case the Error Reporting system |
| 239 | + will use other data, such as remote IP address, |
| 240 | + to distinguish affected users. |
| 241 | +
|
| 242 | + Example:: |
| 243 | +
|
| 244 | + >>> try: |
| 245 | + >>> raise NameError |
| 246 | + >>> except Exception: |
| 247 | + >>> client.report_exception() |
| 248 | + """ |
| 249 | + self._send_error_report(traceback.format_exc(), |
| 250 | + http_context=http_context, |
| 251 | + user=user) |
0 commit comments