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"""Client for interacting with the Google Cloud Logging API."""
16
17import logging
18import os
19import sys
20
21
22import google.api_core.client_options
23from google.cloud.client import ClientWithProject
24from google.cloud.environment_vars import DISABLE_GRPC
25from google.cloud.logging_v2._helpers import _add_defaults_to_filter
26from google.cloud.logging_v2._http import Connection
27from google.cloud.logging_v2._http import _LoggingAPI as JSONLoggingAPI
28from google.cloud.logging_v2._http import _MetricsAPI as JSONMetricsAPI
29from google.cloud.logging_v2._http import _SinksAPI as JSONSinksAPI
30from google.cloud.logging_v2.handlers import CloudLoggingHandler
31from google.cloud.logging_v2.handlers import StructuredLogHandler
32from google.cloud.logging_v2.handlers import setup_logging
33from google.cloud.logging_v2.handlers.handlers import EXCLUDED_LOGGER_DEFAULTS
34from google.cloud.logging_v2.resource import Resource
35from google.cloud.logging_v2.handlers._monitored_resources import detect_resource
36
37
38from google.cloud.logging_v2.logger import Logger
39from google.cloud.logging_v2.metric import Metric
40from google.cloud.logging_v2.sink import Sink
41
42
43_DISABLE_GRPC = os.getenv(DISABLE_GRPC, False)
44_HAVE_GRPC = False
45
46try:
47 if not _DISABLE_GRPC:
48 # only import if DISABLE_GRPC is not set
49 from google.cloud.logging_v2 import _gapic
50
51 _HAVE_GRPC = True
52except ImportError: # pragma: NO COVER
53 # could not import gapic library. Fall back to HTTP mode
54 _HAVE_GRPC = False
55 _gapic = None
56
57_USE_GRPC = _HAVE_GRPC and not _DISABLE_GRPC
58
59_GAE_RESOURCE_TYPE = "gae_app"
60_GKE_RESOURCE_TYPE = "k8s_container"
61_GCF_RESOURCE_TYPE = "cloud_function"
62_RUN_RESOURCE_TYPE = "cloud_run_revision"
63
64
65class Client(ClientWithProject):
66 """Client to bundle configuration needed for API requests."""
67
68 _logging_api = None
69 _sinks_api = None
70 _metrics_api = None
71
72 SCOPE = (
73 "https://www.googleapis.com/auth/logging.read",
74 "https://www.googleapis.com/auth/logging.write",
75 "https://www.googleapis.com/auth/logging.admin",
76 "https://www.googleapis.com/auth/cloud-platform",
77 )
78 """The scopes required for authenticating as a Logging consumer."""
79
80 def __init__(
81 self,
82 *,
83 project=None,
84 credentials=None,
85 _http=None,
86 _use_grpc=None,
87 client_info=None,
88 client_options=None,
89 ):
90 """
91 Args:
92 project (Optional[str]): the project which the client acts on behalf of.
93 If not passed, falls back to the default inferred
94 from the environment.
95 credentials (Optional[google.auth.credentials.Credentials]):
96 Thehe OAuth2 Credentials to use for this
97 client. If not passed (and if no ``_http`` object is
98 passed), falls back to the default inferred from the
99 environment.
100 _http (Optional[requests.Session]): HTTP object to make requests.
101 Can be any object that defines ``request()`` with the same interface as
102 :meth:`requests.Session.request`. If not passed, an
103 ``_http`` object is created that is bound to the
104 ``credentials`` for the current object.
105 This parameter should be considered private, and could
106 change in the future.
107 _use_grpc (Optional[bool]): Explicitly specifies whether
108 to use the gRPC transport or HTTP. If unset,
109 falls back to the ``GOOGLE_CLOUD_DISABLE_GRPC``
110 environment variable
111 This parameter should be considered private, and could
112 change in the future.
113 client_info (Optional[Union[google.api_core.client_info.ClientInfo, google.api_core.gapic_v1.client_info.ClientInfo]]):
114 The client info used to send a user-agent string along with API
115 requests. If ``None``, then default info will be used. Generally,
116 you only need to set this if you're developing your own library
117 or partner tool.
118 client_options (Optional[Union[dict, google.api_core.client_options.ClientOptions]]):
119 Client options used to set user options
120 on the client. API Endpoint should be set through client_options.
121 """
122 super(Client, self).__init__(
123 project=project,
124 credentials=credentials,
125 _http=_http,
126 client_options=client_options,
127 )
128
129 kw_args = {"client_info": client_info}
130 if client_options:
131 if isinstance(client_options, dict):
132 client_options = google.api_core.client_options.from_dict(
133 client_options
134 )
135 if client_options.api_endpoint:
136 api_endpoint = client_options.api_endpoint
137 kw_args["api_endpoint"] = api_endpoint
138
139 self._connection = Connection(self, **kw_args)
140 if client_info is None:
141 # if client info not passed in, use the discovered
142 # client info from _connection object
143 client_info = self._connection._client_info
144
145 self._client_info = client_info
146 self._client_options = client_options
147 if _use_grpc is None:
148 self._use_grpc = _USE_GRPC
149 else:
150 self._use_grpc = _use_grpc
151
152 self._handlers = set()
153
154 @property
155 def logging_api(self):
156 """Helper for logging-related API calls.
157
158 See
159 https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries
160 https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.logs
161 """
162 if self._logging_api is None:
163 if self._use_grpc:
164 self._logging_api = _gapic.make_logging_api(self)
165 else:
166 self._logging_api = JSONLoggingAPI(self)
167 return self._logging_api
168
169 @property
170 def sinks_api(self):
171 """Helper for log sink-related API calls.
172
173 See
174 https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks
175 """
176 if self._sinks_api is None:
177 if self._use_grpc:
178 self._sinks_api = _gapic.make_sinks_api(self)
179 else:
180 self._sinks_api = JSONSinksAPI(self)
181 return self._sinks_api
182
183 @property
184 def metrics_api(self):
185 """Helper for log metric-related API calls.
186
187 See
188 https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics
189 """
190 if self._metrics_api is None:
191 if self._use_grpc:
192 self._metrics_api = _gapic.make_metrics_api(self)
193 else:
194 self._metrics_api = JSONMetricsAPI(self)
195 return self._metrics_api
196
197 def logger(self, name, *, labels=None, resource=None):
198 """Creates a logger bound to the current client.
199
200 Args:
201 name (str): The name of the logger to be constructed.
202 resource (Optional[~logging_v2.Resource]): a monitored resource object
203 representing the resource the code was run on. If not given, will
204 be inferred from the environment.
205 labels (Optional[dict]): Mapping of default labels for entries written
206 via this logger.
207
208 Returns:
209 ~logging_v2.logger.Logger: Logger created with the current client.
210 """
211 return Logger(name, client=self, labels=labels, resource=resource)
212
213 def list_entries(
214 self,
215 *,
216 resource_names=None,
217 filter_=None,
218 order_by=None,
219 max_results=None,
220 page_size=None,
221 page_token=None,
222 ):
223 """Return a generator of log entry resources.
224
225 Args:
226 resource_names (Sequence[str]): Names of one or more parent resources
227 from which to retrieve log entries:
228
229 ::
230
231 "projects/[PROJECT_ID]"
232 "organizations/[ORGANIZATION_ID]"
233 "billingAccounts/[BILLING_ACCOUNT_ID]"
234 "folders/[FOLDER_ID]"
235
236 If not passed, defaults to the project bound to the API's client.
237
238 filter_ (str): a filter expression. See
239 https://cloud.google.com/logging/docs/view/advanced_filters
240 order_by (str) One of :data:`~logging_v2.ASCENDING`
241 or :data:`~logging_v2.DESCENDING`.
242 max_results (Optional[int]):
243 Optional. The maximum number of entries to return.
244 Non-positive values are treated as 0. If None, uses API defaults.
245 page_size (int): number of entries to fetch in each API call. Although
246 requests are paged internally, logs are returned by the generator
247 one at a time. If not passed, defaults to a value set by the API.
248 page_token (str): opaque marker for the starting "page" of entries. If not
249 passed, the API will return the first page of entries.
250
251 Returns:
252 Generator[~logging_v2.LogEntry]
253 """
254 if resource_names is None:
255 resource_names = [f"projects/{self.project}"]
256 filter_ = _add_defaults_to_filter(filter_)
257
258 return self.logging_api.list_entries(
259 resource_names=resource_names,
260 filter_=filter_,
261 order_by=order_by,
262 max_results=max_results,
263 page_size=page_size,
264 page_token=page_token,
265 )
266
267 def sink(self, name, *, filter_=None, destination=None):
268 """Creates a sink bound to the current client.
269
270 Args:
271 name (str): the name of the sink to be constructed.
272 filter_ (Optional[str]): the advanced logs filter expression
273 defining the entries exported by the sink. If not
274 passed, the instance should already exist, to be
275 refreshed via :meth:`Sink.reload`.
276 destination (str): destination URI for the entries exported by
277 the sink. If not passed, the instance should
278 already exist, to be refreshed via
279 :meth:`Sink.reload`.
280
281 Returns:
282 ~logging_v2.sink.Sink: Sink created with the current client.
283 """
284 return Sink(name, filter_=filter_, destination=destination, client=self)
285
286 def list_sinks(
287 self, *, parent=None, max_results=None, page_size=None, page_token=None
288 ):
289 """List sinks for the a parent resource.
290
291 See
292 https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks/list
293
294 Args:
295 parent (Optional[str]): The parent resource whose sinks are to be listed:
296
297 ::
298
299 "projects/[PROJECT_ID]"
300 "organizations/[ORGANIZATION_ID]"
301 "billingAccounts/[BILLING_ACCOUNT_ID]"
302 "folders/[FOLDER_ID]".
303
304 If not passed, defaults to the project bound to the API's client.
305 max_results (Optional[int]):
306 Optional. The maximum number of entries to return.
307 Non-positive values are treated as 0. If None, uses API defaults.
308 page_size (int): number of entries to fetch in each API call. Although
309 requests are paged internally, logs are returned by the generator
310 one at a time. If not passed, defaults to a value set by the API.
311 page_token (str): opaque marker for the starting "page" of entries. If not
312 passed, the API will return the first page of entries.
313
314 Returns:
315 Generator[~logging_v2.Sink]
316 """
317 if parent is None:
318 parent = f"projects/{self.project}"
319 return self.sinks_api.list_sinks(
320 parent=parent,
321 max_results=max_results,
322 page_size=page_size,
323 page_token=page_token,
324 )
325
326 def metric(self, name, *, filter_=None, description=""):
327 """Creates a metric bound to the current client.
328
329 Args:
330 name (str): The name of the metric to be constructed.
331 filter_(Optional[str]): The advanced logs filter expression defining the
332 entries tracked by the metric. If not
333 passed, the instance should already exist, to be
334 refreshed via :meth:`Metric.reload`.
335 description (Optional[str]): The description of the metric to be constructed.
336 If not passed, the instance should already exist,
337 to be refreshed via :meth:`Metric.reload`.
338
339 Returns:
340 ~logging_v2.metric.Metric: Metric created with the current client.
341 """
342 return Metric(name, filter_=filter_, client=self, description=description)
343
344 def list_metrics(self, *, max_results=None, page_size=None, page_token=None):
345 """List metrics for the project associated with this client.
346
347 See
348 https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/list
349
350 Args:
351 max_results (Optional[int]):
352 Optional. The maximum number of entries to return.
353 Non-positive values are treated as 0. If None, uses API defaults.
354 page_size (int): number of entries to fetch in each API call. Although
355 requests are paged internally, logs are returned by the generator
356 one at a time. If not passed, defaults to a value set by the API.
357 page_token (str): opaque marker for the starting "page" of entries. If not
358 passed, the API will return the first page of entries.
359
360 Returns:
361 Generator[logging_v2.Metric]
362 """
363 return self.metrics_api.list_metrics(
364 self.project,
365 max_results=max_results,
366 page_size=page_size,
367 page_token=page_token,
368 )
369
370 def get_default_handler(self, **kw):
371 """Return the default logging handler based on the local environment.
372
373 Args:
374 kw (dict): keyword args passed to handler constructor
375
376 Returns:
377 logging.Handler: The default log handler based on the environment
378 """
379 monitored_resource = kw.pop("resource", detect_resource(self.project))
380
381 if isinstance(monitored_resource, Resource):
382 if monitored_resource.type == _GAE_RESOURCE_TYPE:
383 return CloudLoggingHandler(self, resource=monitored_resource, **kw)
384 elif monitored_resource.type == _GKE_RESOURCE_TYPE:
385 return StructuredLogHandler(**kw, project_id=self.project)
386 elif monitored_resource.type == _GCF_RESOURCE_TYPE:
387 # __stdout__ stream required to support structured logging on Python 3.7
388 kw["stream"] = kw.get("stream", sys.__stdout__)
389 return StructuredLogHandler(**kw, project_id=self.project)
390 elif monitored_resource.type == _RUN_RESOURCE_TYPE:
391 return StructuredLogHandler(**kw, project_id=self.project)
392 return CloudLoggingHandler(self, resource=monitored_resource, **kw)
393
394 def setup_logging(
395 self, *, log_level=logging.INFO, excluded_loggers=EXCLUDED_LOGGER_DEFAULTS, **kw
396 ):
397 """Attach default Cloud Logging handler to the root logger.
398
399 This method uses the default log handler, obtained by
400 :meth:`~get_default_handler`, and attaches it to the root Python
401 logger, so that a call such as ``logging.warn``, as well as all child
402 loggers, will report to Cloud Logging.
403
404 Args:
405 log_level (Optional[int]): The logging level threshold of the attached logger,
406 as set by the :meth:`logging.Logger.setLevel` method. Defaults to
407 :const:`logging.INFO`.
408 excluded_loggers (Optional[Tuple[str]]): The loggers to not attach the
409 handler to. This will always include the
410 loggers in the path of the logging client
411 itself.
412 Returns:
413 dict: keyword args passed to handler constructor
414 """
415 handler = self.get_default_handler(**kw)
416 self._handlers.add(handler)
417 setup_logging(handler, log_level=log_level, excluded_loggers=excluded_loggers)
418
419 def flush_handlers(self):
420 """Flushes all Python log handlers associated with this Client."""
421
422 for handler in self._handlers:
423 handler.flush()
424
425 def close(self):
426 """Closes the Client and all handlers associated with this Client."""
427 super(Client, self).close()
428 for handler in self._handlers:
429 handler.close()