# Copyright 2016 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Client for interacting with the Google Stackdriver Logging API."""
import logging
import os
try:
from google.cloud.logging import _gapic
except ImportError: # pragma: NO COVER
_HAVE_GRPC = False
_gapic = None
else:
_HAVE_GRPC = True
import google.api_core.client_options
from google.cloud.client import ClientWithProject
from google.cloud.environment_vars import DISABLE_GRPC
from google.cloud.logging._helpers import retrieve_metadata_server
from google.cloud.logging._http import Connection
from google.cloud.logging._http import _LoggingAPI as JSONLoggingAPI
from google.cloud.logging._http import _MetricsAPI as JSONMetricsAPI
from google.cloud.logging._http import _SinksAPI as JSONSinksAPI
from google.cloud.logging.handlers import CloudLoggingHandler
from google.cloud.logging.handlers import AppEngineHandler
from google.cloud.logging.handlers import ContainerEngineHandler
from google.cloud.logging.handlers import setup_logging
from google.cloud.logging.handlers.handlers import EXCLUDED_LOGGER_DEFAULTS
from google.cloud.logging.logger import Logger
from google.cloud.logging.metric import Metric
from google.cloud.logging.sink import Sink
_DISABLE_GRPC = os.getenv(DISABLE_GRPC, False)
_USE_GRPC = _HAVE_GRPC and not _DISABLE_GRPC
_APPENGINE_FLEXIBLE_ENV_VM = "GAE_APPENGINE_HOSTNAME"
"""Environment variable set in App Engine when vm:true is set."""
_APPENGINE_INSTANCE_ID = "GAE_INSTANCE"
"""Environment variable set in App Engine standard and flexible environment."""
_GKE_CLUSTER_NAME = "instance/attributes/cluster-name"
"""Attribute in metadata server when in GKE environment."""
[docs]class Client(ClientWithProject):
"""Client to bundle configuration needed for API requests.
:type project: str
:param project: the project which the client acts on behalf of.
If not passed, falls back to the default inferred
from the environment.
:type credentials: :class:`~google.auth.credentials.Credentials`
:param credentials: (Optional) The OAuth2 Credentials to use for this
client. If not passed (and if no ``_http`` object is
passed), falls back to the default inferred from the
environment.
:type _http: :class:`~requests.Session`
:param _http: (Optional) HTTP object to make requests. Can be any object
that defines ``request()`` with the same interface as
:meth:`requests.Session.request`. If not passed, an
``_http`` object is created that is bound to the
``credentials`` for the current object.
This parameter should be considered private, and could
change in the future.
:type _use_grpc: bool
:param _use_grpc: (Optional) Explicitly specifies whether
to use the gRPC transport or HTTP. If unset,
falls back to the ``GOOGLE_CLOUD_DISABLE_GRPC``
environment variable
This parameter should be considered private, and could
change in the future.
:type client_info:
:class:`google.api_core.client_info.ClientInfo` or
:class:`google.api_core.gapic_v1.client_info.ClientInfo`
:param client_info:
The client info used to send a user-agent string along with API
requests. If ``None``, then default info will be used. Generally,
you only need to set this if you're developing your own library
or partner tool.
:type client_options: :class:`~google.api_core.client_options.ClientOptions`
or :class:`dict`
:param client_options: (Optional) Client options used to set user options
on the client. API Endpoint should be set through client_options.
"""
_logging_api = None
_sinks_api = None
_metrics_api = None
SCOPE = (
"https://www.googleapis.com/auth/logging.read",
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/logging.admin",
"https://www.googleapis.com/auth/cloud-platform",
)
"""The scopes required for authenticating as a Logging consumer."""
def __init__(
self,
project=None,
credentials=None,
_http=None,
_use_grpc=None,
client_info=None,
client_options=None,
):
super(Client, self).__init__(
project=project, credentials=credentials, _http=_http
)
kw_args = {"client_info": client_info}
if client_options:
if type(client_options) == dict:
client_options = google.api_core.client_options.from_dict(
client_options
)
if client_options.api_endpoint:
api_endpoint = client_options.api_endpoint
kw_args["api_endpoint"] = api_endpoint
self._connection = Connection(self, **kw_args)
self._client_info = client_info
if _use_grpc is None:
self._use_grpc = _USE_GRPC
else:
self._use_grpc = _use_grpc
@property
def logging_api(self):
"""Helper for logging-related API calls.
See
https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries
https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.logs
"""
if self._logging_api is None:
if self._use_grpc:
self._logging_api = _gapic.make_logging_api(self)
else:
self._logging_api = JSONLoggingAPI(self)
return self._logging_api
@property
def sinks_api(self):
"""Helper for log sink-related API calls.
See
https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks
"""
if self._sinks_api is None:
if self._use_grpc:
self._sinks_api = _gapic.make_sinks_api(self)
else:
self._sinks_api = JSONSinksAPI(self)
return self._sinks_api
@property
def metrics_api(self):
"""Helper for log metric-related API calls.
See
https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics
"""
if self._metrics_api is None:
if self._use_grpc:
self._metrics_api = _gapic.make_metrics_api(self)
else:
self._metrics_api = JSONMetricsAPI(self)
return self._metrics_api
[docs] def logger(self, name):
"""Creates a logger bound to the current client.
:type name: str
:param name: the name of the logger to be constructed.
:rtype: :class:`google.cloud.logging.logger.Logger`
:returns: Logger created with the current client.
"""
return Logger(name, client=self)
[docs] def list_entries(
self,
projects=None,
filter_=None,
order_by=None,
page_size=None,
page_token=None,
):
"""Return a page of log entries.
See
https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/list
:type projects: list of strings
:param projects: project IDs to include. If not passed,
defaults to the project bound to the client.
:type filter_: str
:param filter_:
a filter expression. See
https://cloud.google.com/logging/docs/view/advanced_filters
:type order_by: str
:param order_by: One of :data:`~google.cloud.logging.ASCENDING`
or :data:`~google.cloud.logging.DESCENDING`.
:type page_size: int
:param page_size:
Optional. The maximum number of entries in each page of results
from this request. Non-positive values are ignored. Defaults
to a sensible value set by the API.
:type page_token: str
:param page_token:
Optional. If present, return the next batch of entries, using
the value, which must correspond to the ``nextPageToken`` value
returned in the previous response. Deprecated: use the ``pages``
property of the returned iterator instead of manually passing
the token.
:rtype: :class:`~google.api_core.page_iterator.Iterator`
:returns: Iterator of :class:`~google.cloud.logging.entries._BaseEntry`
accessible to the current client.
"""
if projects is None:
projects = [self.project]
return self.logging_api.list_entries(
projects=projects,
filter_=filter_,
order_by=order_by,
page_size=page_size,
page_token=page_token,
)
[docs] def sink(self, name, filter_=None, destination=None):
"""Creates a sink bound to the current client.
:type name: str
:param name: the name of the sink to be constructed.
:type filter_: str
:param filter_: (optional) the advanced logs filter expression
defining the entries exported by the sink. If not
passed, the instance should already exist, to be
refreshed via :meth:`Sink.reload`.
:type destination: str
:param destination: destination URI for the entries exported by
the sink. If not passed, the instance should
already exist, to be refreshed via
:meth:`Sink.reload`.
:rtype: :class:`google.cloud.logging.sink.Sink`
:returns: Sink created with the current client.
"""
return Sink(name, filter_, destination, client=self)
[docs] def list_sinks(self, page_size=None, page_token=None):
"""List sinks for the project associated with this client.
See
https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks/list
:type page_size: int
:param page_size:
Optional. The maximum number of sinks in each page of results from
this request. Non-positive values are ignored. Defaults to a
sensible value set by the API.
:type page_token: str
:param page_token:
Optional. If present, return the next batch of sinks, using the
value, which must correspond to the ``nextPageToken`` value
returned in the previous response. Deprecated: use the ``pages``
property of the returned iterator instead of manually passing the
token.
:rtype: :class:`~google.api_core.page_iterator.Iterator`
:returns: Iterator of
:class:`~google.cloud.logging.sink.Sink`
accessible to the current client.
"""
return self.sinks_api.list_sinks(self.project, page_size, page_token)
[docs] def metric(self, name, filter_=None, description=""):
"""Creates a metric bound to the current client.
:type name: str
:param name: the name of the metric to be constructed.
:type filter_: str
:param filter_: the advanced logs filter expression defining the
entries tracked by the metric. If not
passed, the instance should already exist, to be
refreshed via :meth:`Metric.reload`.
:type description: str
:param description: the description of the metric to be constructed.
If not passed, the instance should already exist,
to be refreshed via :meth:`Metric.reload`.
:rtype: :class:`google.cloud.logging.metric.Metric`
:returns: Metric created with the current client.
"""
return Metric(name, filter_, client=self, description=description)
[docs] def list_metrics(self, page_size=None, page_token=None):
"""List metrics for the project associated with this client.
See
https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.metrics/list
:type page_size: int
:param page_size:
Optional. The maximum number of metrics in each page of results
from this request. Non-positive values are ignored. Defaults to a
sensible value set by the API.
:type page_token: str
:param page_token:
Optional. If present, return the next batch of metrics, using the
value, which must correspond to the ``nextPageToken`` value
returned in the previous response. Deprecated: use the ``pages``
property of the returned iterator instead of manually passing the
token.
:rtype: :class:`~google.api_core.page_iterator.Iterator`
:returns: Iterator of :class:`~google.cloud.logging.metric.Metric`
accessible to the current client.
"""
return self.metrics_api.list_metrics(self.project, page_size, page_token)
[docs] def get_default_handler(self, **kw):
"""Return the default logging handler based on the local environment.
:type kw: dict
:param kw: keyword args passed to handler constructor
:rtype: :class:`logging.Handler`
:returns: The default log handler based on the environment
"""
gke_cluster_name = retrieve_metadata_server(_GKE_CLUSTER_NAME)
if (
_APPENGINE_FLEXIBLE_ENV_VM in os.environ
or _APPENGINE_INSTANCE_ID in os.environ
):
return AppEngineHandler(self, **kw)
elif gke_cluster_name is not None:
return ContainerEngineHandler(**kw)
else:
return CloudLoggingHandler(self, **kw)
[docs] def setup_logging(
self, log_level=logging.INFO, excluded_loggers=EXCLUDED_LOGGER_DEFAULTS, **kw
):
"""Attach default Stackdriver logging handler to the root logger.
This method uses the default log handler, obtained by
:meth:`~get_default_handler`, and attaches it to the root Python
logger, so that a call such as ``logging.warn``, as well as all child
loggers, will report to Stackdriver logging.
:type log_level: int
:param log_level: (Optional) Python logging log level. Defaults to
:const:`logging.INFO`.
:type excluded_loggers: tuple
:param excluded_loggers: (Optional) The loggers to not attach the
handler to. This will always include the
loggers in the path of the logging client
itself.
:type kw: dict
:param kw: keyword args passed to handler constructor
"""
handler = self.get_default_handler(**kw)
setup_logging(handler, log_level=log_level, excluded_loggers=excluded_loggers)