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"""Interact with Cloud Logging via JSON-over-HTTP."""
16
17import functools
18
19from google.api_core import page_iterator
20from google.cloud import _http
21
22from google.cloud.logging_v2 import __version__
23from google.cloud.logging_v2._helpers import entry_from_resource
24from google.cloud.logging_v2.sink import Sink
25from google.cloud.logging_v2.metric import Metric
26
27
28class Connection(_http.JSONConnection):
29 DEFAULT_API_ENDPOINT = "https://logging.googleapis.com"
30
31 def __init__(self, client, *, client_info=None, api_endpoint=DEFAULT_API_ENDPOINT):
32 """A connection to Google Cloud Logging via the JSON REST API.
33
34 Args:
35 client (google.cloud.logging_v2.cliet.Client):
36 The client that owns the current connection.
37 client_info (Optional[google.api_core.client_info.ClientInfo]):
38 Instance used to generate user agent.
39 client_options (Optional[google.api_core.client_options.ClientOptions]):
40 Client options used to set user options
41 on the client. API Endpoint should be set through client_options.
42 """
43 super(Connection, self).__init__(client, client_info)
44 self.API_BASE_URL = api_endpoint
45 self._client_info.gapic_version = __version__
46 self._client_info.client_library_version = __version__
47
48 API_VERSION = "v2"
49 """The version of the API, used in building the API call's URL."""
50
51 API_URL_TEMPLATE = "{api_base_url}/{api_version}{path}"
52 """A template for the URL of a particular API call."""
53
54
55class _LoggingAPI(object):
56 """Helper mapping logging-related APIs.
57
58 See
59 https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries
60 https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.logs
61
62 :type client: :class:`~google.cloud.logging.client.Client`
63 :param client: The client used to make API requests.
64 """
65
66 def __init__(self, client):
67 self._client = client
68 self.api_request = client._connection.api_request
69
70 def list_entries(
71 self,
72 resource_names,
73 *,
74 filter_=None,
75 order_by=None,
76 max_results=None,
77 page_size=None,
78 page_token=None,
79 ):
80 """Return a page of log entry resources.
81
82 Args:
83 resource_names (Sequence[str]): Names of one or more parent resources
84 from which to retrieve log entries:
85
86 ::
87
88 "projects/[PROJECT_ID]"
89 "organizations/[ORGANIZATION_ID]"
90 "billingAccounts/[BILLING_ACCOUNT_ID]"
91 "folders/[FOLDER_ID]"
92
93 filter_ (str): a filter expression. See
94 https://cloud.google.com/logging/docs/view/advanced_filters
95 order_by (str) One of :data:`~logging_v2.ASCENDING`
96 or :data:`~logging_v2.DESCENDING`.
97 max_results (Optional[int]):
98 Optional. The maximum number of entries to return.
99 Non-positive values are treated as 0. If None, uses API defaults.
100 page_size (int): number of entries to fetch in each API call. Although
101 requests are paged internally, logs are returned by the generator
102 one at a time. If not passed, defaults to a value set by the API.
103 page_token (str): opaque marker for the starting "page" of entries. If not
104 passed, the API will return the first page of entries.
105 Returns:
106 Generator[~logging_v2.LogEntry]
107 """
108 extra_params = {"resourceNames": resource_names}
109
110 if filter_ is not None:
111 extra_params["filter"] = filter_
112
113 if order_by is not None:
114 extra_params["orderBy"] = order_by
115
116 if page_size is not None:
117 extra_params["pageSize"] = page_size
118
119 path = "/entries:list"
120 # We attach a mutable loggers dictionary so that as Logger
121 # objects are created by entry_from_resource, they can be
122 # re-used by other log entries from the same logger.
123 loggers = {}
124 item_to_value = functools.partial(_item_to_entry, loggers=loggers)
125 iterator = page_iterator.HTTPIterator(
126 client=self._client,
127 api_request=self._client._connection.api_request,
128 path=path,
129 item_to_value=item_to_value,
130 items_key="entries",
131 page_token=page_token,
132 extra_params=extra_params,
133 )
134 # This method uses POST to make a read-only request.
135 iterator._HTTP_METHOD = "POST"
136
137 return _entries_pager(iterator, max_results)
138
139 def write_entries(
140 self,
141 entries,
142 *,
143 logger_name=None,
144 resource=None,
145 labels=None,
146 partial_success=True,
147 dry_run=False,
148 ):
149 """Log an entry resource via a POST request
150
151 See
152 https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/write
153
154 Args:
155 entries (Sequence[Mapping[str, ...]]): sequence of mappings representing
156 the log entry resources to log.
157 logger_name (Optional[str]): name of default logger to which to log the entries;
158 individual entries may override.
159 resource(Optional[Mapping[str, ...]]): default resource to associate with entries;
160 individual entries may override.
161 labels (Optional[Mapping[str, ...]]): default labels to associate with entries;
162 individual entries may override.
163 partial_success (Optional[bool]): Whether valid entries should be written even if
164 some other entries fail due to INVALID_ARGUMENT or
165 PERMISSION_DENIED errors. If any entry is not written, then
166 the response status is the error associated with one of the
167 failed entries and the response includes error details keyed
168 by the entries' zero-based index in the ``entries.write``
169 method.
170 dry_run (Optional[bool]):
171 If true, the request should expect normal response,
172 but the entries won't be persisted nor exported.
173 Useful for checking whether the logging API endpoints are working
174 properly before sending valuable data.
175 """
176 data = {
177 "entries": list(entries),
178 "partialSuccess": partial_success,
179 "dry_run": dry_run,
180 }
181
182 if logger_name is not None:
183 data["logName"] = logger_name
184
185 if resource is not None:
186 data["resource"] = resource
187
188 if labels is not None:
189 data["labels"] = labels
190
191 self.api_request(method="POST", path="/entries:write", data=data)
192
193 def logger_delete(self, logger_name):
194 """Delete all entries in a logger.
195
196 Args:
197 logger_name (str): The resource name of the log to delete:
198
199 ::
200
201 "projects/[PROJECT_ID]/logs/[LOG_ID]"
202 "organizations/[ORGANIZATION_ID]/logs/[LOG_ID]"
203 "billingAccounts/[BILLING_ACCOUNT_ID]/logs/[LOG_ID]"
204 "folders/[FOLDER_ID]/logs/[LOG_ID]"
205
206 ``[LOG_ID]`` must be URL-encoded. For example,
207 ``"projects/my-project-id/logs/syslog"``,
208 ``"organizations/1234567890/logs/cloudresourcemanager.googleapis.com%2Factivity"``.
209 """
210 path = f"/{logger_name}"
211 self.api_request(method="DELETE", path=path)
212
213
214class _SinksAPI(object):
215 """Helper mapping sink-related APIs.
216
217 See
218 https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks
219 """
220
221 def __init__(self, client):
222 self._client = client
223 self.api_request = client._connection.api_request
224
225 def list_sinks(self, parent, *, max_results=None, page_size=None, page_token=None):
226 """List sinks for the parent resource.
227
228 See
229 https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks/list
230
231 Args:
232 parent (str): The parent resource whose sinks are to be listed:
233
234 ::
235
236 "projects/[PROJECT_ID]"
237 "organizations/[ORGANIZATION_ID]"
238 "billingAccounts/[BILLING_ACCOUNT_ID]"
239 "folders/[FOLDER_ID]".
240 max_results (Optional[int]):
241 Optional. The maximum number of entries to return.
242 Non-positive values are treated as 0. If None, uses API defaults.
243 page_size (int): number of entries to fetch in each API call. Although
244 requests are paged internally, logs are returned by the generator
245 one at a time. If not passed, defaults to a value set by the API.
246 page_token (str): opaque marker for the starting "page" of entries. If not
247 passed, the API will return the first page of entries.
248
249 Returns:
250 Generator[~logging_v2.Sink]
251 """
252 extra_params = {}
253
254 if page_size is not None:
255 extra_params["pageSize"] = page_size
256
257 path = f"/{parent}/sinks"
258 iterator = page_iterator.HTTPIterator(
259 client=self._client,
260 api_request=self._client._connection.api_request,
261 path=path,
262 item_to_value=_item_to_sink,
263 items_key="sinks",
264 page_token=page_token,
265 extra_params=extra_params,
266 )
267
268 return _entries_pager(iterator, max_results)
269
270 def sink_create(
271 self, parent, sink_name, filter_, destination, *, unique_writer_identity=False
272 ):
273 """Create a sink resource.
274
275 See
276 https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks/create
277
278 Args:
279 parent(str): The resource in which to create the sink:
280
281 ::
282
283 "projects/[PROJECT_ID]"
284 "organizations/[ORGANIZATION_ID]"
285 "billingAccounts/[BILLING_ACCOUNT_ID]"
286 "folders/[FOLDER_ID]".
287 sink_name (str): The name of the sink.
288 filter_ (str): The advanced logs filter expression defining the
289 entries exported by the sink.
290 destination (str): Destination URI for the entries exported by
291 the sink.
292 unique_writer_identity (Optional[bool]): determines the kind of
293 IAM identity returned as writer_identity in the new sink.
294
295 Returns:
296 dict: The sink resource returned from the API.
297 """
298 target = f"/{parent}/sinks"
299 data = {"name": sink_name, "filter": filter_, "destination": destination}
300 query_params = {"uniqueWriterIdentity": unique_writer_identity}
301 return self.api_request(
302 method="POST", path=target, data=data, query_params=query_params
303 )
304
305 def sink_get(self, sink_name):
306 """Retrieve a sink resource.
307
308 Args:
309 sink_name (str): The resource name of the sink:
310
311 ::
312
313 "projects/[PROJECT_ID]/sinks/[SINK_ID]"
314 "organizations/[ORGANIZATION_ID]/sinks/[SINK_ID]"
315 "billingAccounts/[BILLING_ACCOUNT_ID]/sinks/[SINK_ID]"
316 "folders/[FOLDER_ID]/sinks/[SINK_ID]"
317
318 Returns:
319 dict: The JSON sink object returned from the API.
320 """
321 target = f"/{sink_name}"
322 return self.api_request(method="GET", path=target)
323
324 def sink_update(
325 self, sink_name, filter_, destination, *, unique_writer_identity=False
326 ):
327 """Update a sink resource.
328
329 Args:
330 sink_name (str): Required. The resource name of the sink:
331
332 ::
333
334 "projects/[PROJECT_ID]/sinks/[SINK_ID]"
335 "organizations/[ORGANIZATION_ID]/sinks/[SINK_ID]"
336 "billingAccounts/[BILLING_ACCOUNT_ID]/sinks/[SINK_ID]"
337 "folders/[FOLDER_ID]/sinks/[SINK_ID]"
338 filter_ (str): The advanced logs filter expression defining the
339 entries exported by the sink.
340 destination (str): destination URI for the entries exported by
341 the sink.
342 unique_writer_identity (Optional[bool]): determines the kind of
343 IAM identity returned as writer_identity in the new sink.
344
345
346 Returns:
347 dict: The returned (updated) resource.
348 """
349 target = f"/{sink_name}"
350 name = sink_name.split("/")[-1] # parse name out of full resource name
351 data = {"name": name, "filter": filter_, "destination": destination}
352 query_params = {"uniqueWriterIdentity": unique_writer_identity}
353 return self.api_request(
354 method="PUT", path=target, query_params=query_params, data=data
355 )
356
357 def sink_delete(self, sink_name):
358 """Delete a sink resource.
359
360 Args:
361 sink_name (str): Required. The full resource name of the sink to delete,
362 including the parent resource and the sink identifier:
363
364 ::
365
366 "projects/[PROJECT_ID]/sinks/[SINK_ID]"
367 "organizations/[ORGANIZATION_ID]/sinks/[SINK_ID]"
368 "billingAccounts/[BILLING_ACCOUNT_ID]/sinks/[SINK_ID]"
369 "folders/[FOLDER_ID]/sinks/[SINK_ID]"
370
371 Example: ``"projects/my-project-id/sinks/my-sink-id"``.
372 """
373 target = f"/{sink_name}"
374 self.api_request(method="DELETE", path=target)
375
376
377class _MetricsAPI(object):
378 """Helper mapping sink-related APIs."""
379
380 def __init__(self, client):
381 self._client = client
382 self.api_request = client._connection.api_request
383
384 def list_metrics(
385 self, project, *, max_results=None, page_size=None, page_token=None
386 ):
387 """List metrics for the project associated with this client.
388
389 See
390 https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/list
391
392 Args:
393 max_results (Optional[int]):
394 Optional. The maximum number of entries to return.
395 Non-positive values are treated as 0. If None, uses API defaults.
396 page_size (int): number of entries to fetch in each API call. Although
397 requests are paged internally, logs are returned by the generator
398 one at a time. If not passed, defaults to a value set by the API.
399 page_token (str): opaque marker for the starting "page" of entries. If not
400 passed, the API will return the first page of entries.
401
402 Returns:
403 Generator[logging_v2.Metric]
404
405 """
406 extra_params = {}
407
408 if page_size is not None:
409 extra_params["pageSize"] = page_size
410
411 path = f"/projects/{project}/metrics"
412 iterator = page_iterator.HTTPIterator(
413 client=self._client,
414 api_request=self._client._connection.api_request,
415 path=path,
416 item_to_value=_item_to_metric,
417 items_key="metrics",
418 page_token=page_token,
419 extra_params=extra_params,
420 )
421 return _entries_pager(iterator, max_results)
422
423 def metric_create(self, project, metric_name, filter_, description):
424 """Create a metric resource.
425
426 See
427 https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/create
428
429 Args:
430 project (str): ID of the project in which to create the metric.
431 metric_name (str): The name of the metric
432 filter_ (str): The advanced logs filter expression defining the
433 entries exported by the metric.
434 description (str): description of the metric.
435 """
436 target = f"/projects/{project}/metrics"
437 data = {"name": metric_name, "filter": filter_, "description": description}
438 self.api_request(method="POST", path=target, data=data)
439
440 def metric_get(self, project, metric_name):
441 """Retrieve a metric resource.
442
443 Args:
444 project (str): ID of the project containing the metric.
445 metric_name (str): The name of the metric
446
447 Returns:
448 dict: The JSON metric object returned from the API.
449 """
450 target = f"/projects/{project}/metrics/{metric_name}"
451 return self.api_request(method="GET", path=target)
452
453 def metric_update(self, project, metric_name, filter_, description):
454 """Update a metric resource.
455
456 See
457 https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/update
458
459 Args:
460 project (str): ID of the project containing the metric.
461 metric_name (str): the name of the metric
462 filter_ (str): the advanced logs filter expression defining the
463 entries exported by the metric.
464 description (str): description of the metric.
465
466 Returns:
467 dict: The returned (updated) resource.
468 """
469 target = f"/projects/{project}/metrics/{metric_name}"
470 data = {"name": metric_name, "filter": filter_, "description": description}
471 return self.api_request(method="PUT", path=target, data=data)
472
473 def metric_delete(self, project, metric_name):
474 """Delete a metric resource.
475
476 Args:
477 project (str): ID of the project containing the metric.
478 metric_name (str): The name of the metric
479 """
480 target = f"/projects/{project}/metrics/{metric_name}"
481 self.api_request(method="DELETE", path=target)
482
483
484def _entries_pager(page_iter, max_results=None):
485 if max_results is not None and max_results < 0:
486 raise ValueError("max_results must be positive")
487
488 i = 0
489 for page in page_iter:
490 if max_results is not None and i >= max_results:
491 break
492 yield page
493 i += 1
494
495
496def _item_to_entry(iterator, resource, loggers):
497 """Convert a log entry resource to the native object.
498
499 .. note::
500
501 This method does not have the correct signature to be used as
502 the ``item_to_value`` argument to
503 :class:`~google.api_core.page_iterator.Iterator`. It is intended to be
504 patched with a mutable ``loggers`` argument that can be updated
505 on subsequent calls. For an example, see how the method is
506 used above in :meth:`_LoggingAPI.list_entries`.
507
508 Args:
509 iterator (google.api_core.page_iterator.Iterator): The iterator that
510 is currently in use.
511 resource (dict): Log entry JSON resource returned from the API.
512 loggers (Mapping[str, logging_v2.logger.Logger]):
513 A mapping of logger fullnames -> loggers. If the logger
514 that owns the entry is not in ``loggers``, the entry
515 will have a newly-created logger.
516
517 Returns:
518 ~logging_v2.entries._BaseEntry: The next log entry in the page.
519 """
520 return entry_from_resource(resource, iterator.client, loggers)
521
522
523def _item_to_sink(iterator, resource):
524 """Convert a sink resource to the native object.
525
526 Args:
527 iterator (google.api_core.page_iterator.Iterator): The iterator that
528 is currently in use.
529 resource (dict): Sink JSON resource returned from the API.
530
531 Returns:
532 ~logging_v2.sink.Sink: The next sink in the page.
533 """
534 return Sink.from_api_repr(resource, iterator.client)
535
536
537def _item_to_metric(iterator, resource):
538 """Convert a metric resource to the native object.
539
540 Args:
541 iterator (google.api_core.page_iterator.Iterator): The iterator that
542 is currently in use.
543 resource (dict): Sink JSON resource returned from the API.
544
545 Returns:
546 ~logging_v2.metric.Metric:
547 The next metric in the page.
548 """
549 return Metric.from_api_repr(resource, iterator.client)