diff --git a/docs/usage.rst b/docs/usage.rst index 4714144f..1ea9440f 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -8,21 +8,21 @@ To write log entries, first create a :class:`~google.cloud.logging.logger.Logger`, passing the "log name" with which to associate the entries: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START logger_create] :end-before: [END logger_create] :dedent: 4 Write a simple text entry to the logger. -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START logger_log_text] :end-before: [END logger_log_text] :dedent: 4 Write a dictionary entry to the logger. -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START logger_log_struct] :end-before: [END logger_log_struct] :dedent: 4 @@ -34,7 +34,7 @@ Supported Resource values are listed at `Monitored Resource Types`_ .. _Monitored Resource Types: https://cloud.google.com/logging/docs/api/v2/resource-list -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START logger_log_resource_text] :end-before: [END logger_log_resource_text] :dedent: 4 @@ -44,7 +44,7 @@ Retrieving log entries Fetch entries for the default project. -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START client_list_entries_default] :end-before: [END client_list_entries_default] :dedent: 4 @@ -59,41 +59,27 @@ will be instances of one of the following classes: - :class:`~google.cloud.logging.entries.StructEntry` - :class:`~google.cloud.logging.entries.ProtobufEntry` -Fetch entries across multiple projects. - -.. literalinclude:: snippets.py - :start-after: [START client_list_entries_multi_project] - :end-before: [END client_list_entries_multi_project] - :dedent: 4 - Filter entries retrieved using the `Advanced Logs Filters`_ syntax .. _Advanced Logs Filters: https://cloud.google.com/logging/docs/view/advanced_filters Fetch entries for the default project. -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START client_list_entries_filter] :end-before: [END client_list_entries_filter] :dedent: 4 Sort entries in descending timestamp order. -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START client_list_entries_order_by] :end-before: [END client_list_entries_order_by] :dedent: 4 -Retrieve entries in batches of 10, iterating until done. - -.. literalinclude:: snippets.py - :start-after: [START client_list_entries_paged] - :end-before: [END client_list_entries_paged] - :dedent: 4 - Retrieve entries for a single logger, sorting in descending timestamp order: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START logger_list_entries] :end-before: [END logger_list_entries] :dedent: 4 @@ -102,7 +88,7 @@ Retrieve entries for a single logger, sorting in descending timestamp order: Delete all entries for a logger ------------------------------- -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START logger_delete] :end-before: [END logger_delete] :dedent: 8 @@ -116,35 +102,35 @@ used within Cloud Monitoring to create charts and alerts. List all metrics for a project: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START client_list_metrics] :end-before: [END client_list_metrics] :dedent: 4 Create a metric: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START metric_create] :end-before: [END metric_create] :dedent: 4 Refresh local information about a metric: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START metric_reload] :end-before: [END metric_reload] :dedent: 4 Update a metric: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START metric_update] :end-before: [END metric_update] :dedent: 4 Delete a metric: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START metric_delete] :end-before: [END metric_delete] :dedent: 4 @@ -166,14 +152,14 @@ Make sure that the storage bucket you want to export logs too has Add ``cloud-logs@google.com`` as the owner of the bucket: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START sink_bucket_permissions] :end-before: [END sink_bucket_permissions] :dedent: 4 Create a Cloud Storage sink: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START sink_storage_create] :end-before: [END sink_storage_create] :dedent: 4 @@ -189,14 +175,14 @@ See: `Setting permissions for BigQuery`_ .. _Setting permissions for BigQuery: https://cloud.google.com/logging/docs/export/configure_export_v2#errors_exporting_to_bigquery -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START sink_dataset_permissions] :end-before: [END sink_dataset_permissions] :dedent: 4 Create a BigQuery sink: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START sink_bigquery_create] :end-before: [END sink_bigquery_create] :dedent: 4 @@ -212,14 +198,14 @@ See: `Setting permissions for Pub/Sub`_ .. _Setting permissions for Pub/Sub: https://cloud.google.com/logging/docs/export/configure_export_v2#errors_exporting_logs_to_cloud_pubsub -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START sink_topic_permissions] :end-before: [END sink_topic_permissions] :dedent: 4 Create a Cloud Pub/Sub sink: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START sink_pubsub_create] :end-before: [END sink_pubsub_create] :dedent: 4 @@ -229,28 +215,28 @@ Manage Sinks List all sinks for a project: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START client_list_sinks] :end-before: [END client_list_sinks] :dedent: 4 Refresh local information about a sink: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START sink_reload] :end-before: [END sink_reload] :dedent: 4 Update a sink: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START sink_update] :end-before: [END sink_update] :dedent: 4 Delete a sink: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START sink_delete] :end-before: [END sink_delete] :dedent: 4 @@ -263,7 +249,7 @@ Cloud Logging. There are different handler options to accomplish this. To automatically pick the default for your current environment, use :meth:`~google.cloud.logging.client.Client.get_default_handler`. -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START create_default_handler] :end-before: [END create_default_handler] :dedent: 4 @@ -274,7 +260,7 @@ as well as any other loggers created. A helper method :meth:`~google.cloud.logging.client.Client.setup_logging` is provided to configure this automatically. -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START setup_logging] :end-before: [END setup_logging] :dedent: 4 @@ -286,7 +272,7 @@ to configure this automatically. You can also exclude certain loggers: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START setup_logging_excludes] :end-before: [END setup_logging_excludes] :dedent: 4 @@ -300,7 +286,7 @@ directly create a :class:`~google.cloud.logging.handlers.handlers.CloudLoggingHandler` instance which will write directly to the API. -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START create_cloud_handler] :end-before: [END create_cloud_handler] :dedent: 4 @@ -316,7 +302,7 @@ All logs will go to a single custom log, which defaults to "python". The name of the Python logger will be included in the structured log entry under the "python_logger" field. You can change it by providing a name to the handler: -.. literalinclude:: snippets.py +.. literalinclude:: ../samples/snippets/usage_guide.py :start-after: [START create_named_handler] :end-before: [END create_named_handler] :dedent: 4 diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index bdb659d0..b071a67f 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1 +1,4 @@ -google-cloud-logging==2.0.1 +google-cloud-logging==2.0.2 +google-cloud-storage==1.35.0 +google-cloud-pubsub==2.2.0 +google-cloud-bigquery==2.6.1 diff --git a/docs/snippets.py b/samples/snippets/usage_guide.py similarity index 65% rename from docs/snippets.py rename to samples/snippets/usage_guide.py index da9ba9b2..b28d1098 100644 --- a/docs/snippets.py +++ b/samples/snippets/usage_guide.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Testable usage examples for Cloud Logging API wrapper +"""Samples embedded in the Usage Guide (docs/usage.rst) Each example function takes a ``client`` argument (which must be an instance of :class:`google.cloud.logging.client.Client`) and uses it to perform a task @@ -23,9 +23,10 @@ need to be deleted during teardown. """ +import os import time -from google.cloud.logging.client import Client +from google.cloud.logging import Client def snippet(func): @@ -42,25 +43,6 @@ def do_something_with(item): # pylint: disable=unused-argument pass -# pylint: disable=reimported,unused-variable,unused-argument -@snippet -def instantiate_client(_unused_client, _unused_to_delete): - """Instantiate client.""" - - # [START client_create_default] - from google.cloud import logging - - client = logging.Client() - # [END client_create_default] - - credentials = object() - # [START client_create_explicit] - from google.cloud import logging - - client = logging.Client(project="my-project", credentials=credentials) - # [END client_create_explicit] - - # pylint: enable=reimported,unused-variable,unused-argument @@ -71,55 +53,32 @@ def client_list_entries(client, to_delete): # pylint: disable=unused-argument # [START client_list_entries_default] for entry in client.list_entries(): # API call(s) do_something_with(entry) - # [END client_list_entries_default] + # [END client_list_entries_default] + break # [START client_list_entries_filter] - FILTER = "logName:log_name AND textPayload:simple" - for entry in client.list_entries(filter_=FILTER): # API call(s) + filter_str = "logName:log_name AND textPayload:simple" + for entry in client.list_entries(filter_=filter_str): # API call(s) do_something_with(entry) - # [END client_list_entries_filter] + # [END client_list_entries_filter] + break # [START client_list_entries_order_by] from google.cloud.logging import DESCENDING for entry in client.list_entries(order_by=DESCENDING): # API call(s) do_something_with(entry) - # [END client_list_entries_order_by] - - # [START client_list_entries_paged] - iterator = client.list_entries() - pages = iterator.pages - - page1 = next(pages) # API call - for entry in page1: - do_something_with(entry) - - page2 = next(pages) # API call - for entry in page2: - do_something_with(entry) - # [END client_list_entries_paged] - - -# @snippet Commented because we need real project IDs to test -def client_list_entries_multi_project( - client, to_delete -): # pylint: disable=unused-argument - """List entries via client across multiple projects.""" - - # [START client_list_entries_multi_project] - resource_names = ["projects/one-project", "projects/another-project"] - for entry in client.list_entries(resource_names=resource_names): # API call(s) - do_something_with(entry) - # [END client_list_entries_multi_project] + # [END client_list_entries_order_by] + break @snippet def logger_usage(client, to_delete): """Logger usage.""" - LOG_NAME = "logger_usage_%d" % (_millis()) + log_name = "logger_usage_%d" % (_millis()) # [START logger_create] - logger = client.logger(LOG_NAME) + logger = client.logger(log_name) # [END logger_create] to_delete.append(logger) @@ -134,7 +93,7 @@ def logger_usage(client, to_delete): # [END logger_log_struct] # [START logger_log_resource_text] - from google.cloud.logging.resource import Resource + from google.cloud.logging import Resource res = Resource( type="generic_node", @@ -168,11 +127,11 @@ def _logger_delete(): @snippet def metric_crud(client, to_delete): """Metric CRUD.""" - METRIC_NAME = "robots-%d" % (_millis(),) - DESCRIPTION = "Robots all up in your server" - FILTER = "logName:apache-access AND textPayload:robot" - UPDATED_FILTER = "textPayload:robot" - UPDATED_DESCRIPTION = "Danger, Will Robinson!" + metric_name = "robots-%d" % (_millis(),) + description = "Robots all up in your server" + filter = "logName:apache-access AND textPayload:robot" + updated_filter = "textPayload:robot" + updated_description = "Danger, Will Robinson!" # [START client_list_metrics] for metric in client.list_metrics(): # API call(s) @@ -180,7 +139,7 @@ def metric_crud(client, to_delete): # [END client_list_metrics] # [START metric_create] - metric = client.metric(METRIC_NAME, filter_=FILTER, description=DESCRIPTION) + metric = client.metric(metric_name, filter_=filter, description=description) assert not metric.exists() # API call metric.create() # API call assert metric.exists() # API call @@ -188,20 +147,20 @@ def metric_crud(client, to_delete): to_delete.append(metric) # [START metric_reload] - existing_metric = client.metric(METRIC_NAME) + existing_metric = client.metric(metric_name) existing_metric.reload() # API call # [END metric_reload] - assert existing_metric.filter_ == FILTER - assert existing_metric.description == DESCRIPTION + assert existing_metric.filter_ == filter + assert existing_metric.description == description # [START metric_update] - existing_metric.filter_ = UPDATED_FILTER - existing_metric.description = UPDATED_DESCRIPTION + existing_metric.filter_ = updated_filter + existing_metric.description = updated_description existing_metric.update() # API call # [END metric_update] existing_metric.reload() - assert existing_metric.filter_ == UPDATED_FILTER - assert existing_metric.description == UPDATED_DESCRIPTION + assert existing_metric.filter_ == updated_filter + assert existing_metric.description == updated_description def _metric_delete(): # [START metric_delete] @@ -215,9 +174,9 @@ def _metric_delete(): def _sink_storage_setup(client): from google.cloud import storage - BUCKET_NAME = "sink-storage-%d" % (_millis(),) + bucket_name = "sink-storage-%d" % (_millis(),) client = storage.Client() - bucket = client.bucket(BUCKET_NAME) + bucket = client.bucket(bucket_name) bucket.create() # [START sink_bucket_permissions] @@ -236,12 +195,12 @@ def sink_storage(client, to_delete): """Sink log entries to storage.""" bucket = _sink_storage_setup(client) to_delete.append(bucket) - SINK_NAME = "robots-storage-%d" % (_millis(),) - FILTER = "textPayload:robot" + sink_name = "robots-storage-%d" % (_millis(),) + filter = "textPayload:robot" # [START sink_storage_create] - DESTINATION = "storage.googleapis.com/%s" % (bucket.name,) - sink = client.sink(SINK_NAME, filter_=FILTER, destination=DESTINATION) + destination = "storage.googleapis.com/%s" % (bucket.name,) + sink = client.sink(sink_name, filter_=filter, destination=destination) assert not sink.exists() # API call sink.create() # API call assert sink.exists() # API call @@ -252,19 +211,17 @@ def sink_storage(client, to_delete): def _sink_bigquery_setup(client): from google.cloud import bigquery - DATASET_NAME = "sink_bigquery_%d" % (_millis(),) + dataset_name = "sink_bigquery_%d" % (_millis(),) client = bigquery.Client() - dataset = client.dataset(DATASET_NAME) - dataset.create() - dataset.reload() + dataset = client.create_dataset(dataset_name) # [START sink_dataset_permissions] - from google.cloud.bigquery.dataset import AccessGrant + from google.cloud.bigquery.dataset import AccessEntry - grants = dataset.access_grants - grants.append(AccessGrant("WRITER", "groupByEmail", "cloud-logs@google.com")) - dataset.access_grants = grants - dataset.update() # API call + entry_list = dataset.access_entries + entry_list.append(AccessEntry("WRITER", "groupByEmail", "cloud-logs@google.com")) + dataset.access_entries = entry_list + client.update_dataset(dataset, ["access_entries"]) # API call # [END sink_dataset_permissions] return dataset @@ -274,13 +231,12 @@ def _sink_bigquery_setup(client): def sink_bigquery(client, to_delete): """Sink log entries to bigquery.""" dataset = _sink_bigquery_setup(client) - to_delete.append(dataset) - SINK_NAME = "robots-bigquery-%d" % (_millis(),) - FILTER = "textPayload:robot" + sink_name = "robots-bigquery-%d" % (_millis(),) + filter_str = "textPayload:robot" # [START sink_bigquery_create] - DESTINATION = "bigquery.googleapis.com%s" % (dataset.path,) - sink = client.sink(SINK_NAME, filter_=FILTER, destination=DESTINATION) + destination = "bigquery.googleapis.com%s" % (dataset.path,) + sink = client.sink(sink_name, filter_=filter_str, destination=destination) assert not sink.exists() # API call sink.create() # API call assert sink.exists() # API call @@ -291,15 +247,21 @@ def sink_bigquery(client, to_delete): def _sink_pubsub_setup(client): from google.cloud import pubsub - TOPIC_NAME = "sink-pubsub-%d" % (_millis(),) - client = pubsub.Client() - topic = client.topic(TOPIC_NAME) - topic.create() + client = pubsub.PublisherClient() + + project_id = os.environ["GOOGLE_CLOUD_PROJECT"] + topic_id = "sink-pubsub-%d" % (_millis(),) # [START sink_topic_permissions] - policy = topic.get_iam_policy() # API call - policy.owners.add(policy.group("cloud-logs@google.com")) - topic.set_iam_policy(policy) # API call + topic_path = client.topic_path(project_id, topic_id) + topic = client.create_topic(request={"name": topic_path}) + + policy = client.get_iam_policy(request={"resource": topic_path}) # API call + policy.bindings.add(role="roles/owner", members=["group:cloud-logs@google.com"]) + + client.set_iam_policy( + request={"resource": topic_path, "policy": policy} + ) # API call # [END sink_topic_permissions] return topic @@ -309,19 +271,18 @@ def _sink_pubsub_setup(client): def sink_pubsub(client, to_delete): """Sink log entries to pubsub.""" topic = _sink_pubsub_setup(client) - to_delete.append(topic) - SINK_NAME = "robots-pubsub-%d" % (_millis(),) - FILTER = "logName:apache-access AND textPayload:robot" - UPDATED_FILTER = "textPayload:robot" + sink_name = "robots-pubsub-%d" % (_millis(),) + filter_str = "logName:apache-access AND textPayload:robot" + updated_filter = "textPayload:robot" # [START sink_pubsub_create] - DESTINATION = "pubsub.googleapis.com/%s" % (topic.full_name,) - sink = client.sink(SINK_NAME, filter_=FILTER, destination=DESTINATION) + destination = "pubsub.googleapis.com/%s" % (topic.name,) + sink = client.sink(sink_name, filter_=filter_str, destination=destination) assert not sink.exists() # API call sink.create() # API call assert sink.exists() # API call # [END sink_pubsub_create] - to_delete.insert(0, sink) # delete sink before topic + created_sink = sink # [START client_list_sinks] for sink in client.list_sinks(): # API call(s) @@ -329,23 +290,23 @@ def sink_pubsub(client, to_delete): # [END client_list_sinks] # [START sink_reload] - existing_sink = client.sink(SINK_NAME) + existing_sink = client.sink(sink_name) existing_sink.reload() # [END sink_reload] - assert existing_sink.filter_ == FILTER - assert existing_sink.destination == DESTINATION + assert existing_sink.filter_ == filter_str + assert existing_sink.destination == destination # [START sink_update] - existing_sink.filter_ = UPDATED_FILTER + existing_sink.filter_ = updated_filter existing_sink.update() # [END sink_update] existing_sink.reload() - assert existing_sink.filter_ == UPDATED_FILTER + assert existing_sink.filter_ == updated_filter + sink = created_sink # [START sink_delete] sink.delete() # [END sink_delete] - to_delete.pop(0) @snippet diff --git a/samples/snippets/usage_guide_test.py b/samples/snippets/usage_guide_test.py new file mode 100644 index 00000000..f02d82fb --- /dev/null +++ b/samples/snippets/usage_guide_test.py @@ -0,0 +1,90 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + + +from google.cloud.logging import Client + +import usage_guide + + +def test_logger_usage(): + client = Client() + + to_delete = [] + usage_guide.logger_usage(client, to_delete) + + for item in to_delete: + usage_guide._backoff_not_found(item.delete) + + +def test_metric_crud(): + client = Client() + + to_delete = [] + usage_guide.metric_crud(client, to_delete) + + for item in to_delete: + usage_guide._backoff_not_found(item.delete) + + +def test_sink_storage(): + client = Client() + + to_delete = [] + usage_guide.sink_storage(client, to_delete) + + for item in to_delete: + usage_guide._backoff_not_found(item.delete) + + +def test_sink_bigquery(): + client = Client() + + to_delete = [] + usage_guide.sink_bigquery(client, to_delete) + + for item in to_delete: + usage_guide._backoff_not_found(item.delete) + + +def test_sink_pubsub(): + client = Client() + + to_delete = [] + usage_guide.sink_pubsub(client, to_delete) + + for item in to_delete: + usage_guide._backoff_not_found(item.delete) + + +def test_logging_handler(): + client = Client() + + usage_guide.logging_handler(client) + + +def test_setup_logging(): + client = Client() + + usage_guide.setup_logging(client) + + +def test_client_list_entries(): + client = Client() + + to_delete = [] + usage_guide.client_list_entries(client, to_delete) + + for item in to_delete: + usage_guide._backoff_not_found(item.delete)