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)