# -*- coding: utf-8 -*- # # Copyright 2015 Google LLC. 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. """Displays log entries produced by Google Cloud Functions.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import datetime from googlecloudsdk.api_lib.functions.v1 import util as util_v1 from googlecloudsdk.api_lib.functions.v2 import client as client_v2 from googlecloudsdk.api_lib.logging import common as logging_common from googlecloudsdk.api_lib.logging import util as logging_util from googlecloudsdk.calliope import arg_parsers from googlecloudsdk.calliope import base from googlecloudsdk.calliope import parser_extensions from googlecloudsdk.command_lib.functions import flags from googlecloudsdk.command_lib.functions import util from googlecloudsdk.core import log from googlecloudsdk.core import properties from googlecloudsdk.core import resources import six _DEFAULT_TABLE_FORMAT = 'table(level,name,execution_id,time_utc,log)' def _GetFunctionRef(name): # type: (str) -> resources.Resource | None if not name: return None return resources.REGISTRY.Parse( name, params={ 'projectsId': properties.VALUES.core.project.GetOrFail(), 'locationsId': properties.VALUES.functions.region.GetOrFail(), }, collection='cloudfunctions.projects.locations.functions', ) def _CreateGen1LogFilterBase(function_ref, region): """Generates Gen1-specific log filter base.""" log_filter = [ 'resource.type="cloud_function"', 'resource.labels.region="{}"'.format(region), 'logName:"cloud-functions"', ] if function_ref: function_id = function_ref.functionsId log_filter.append('resource.labels.function_name="{}"'.format(function_id)) return ' '.join(log_filter) def _CreateGen2LogFilterBase(function_ref, region): """Generates Gen2-specific log filter base.""" log_filter = [ 'resource.type="cloud_run_revision"', 'resource.labels.location="{}"'.format(region), 'logName:"run.googleapis.com"', 'labels."goog-managed-by"="cloudfunctions"', ] if function_ref: # To conform to Cloud Run resource formats, GCFv2 functions' service names # are the function ID lower-cased with '_' replaced with '-'. # Context: go/upper-case-function-ids service_name = function_ref.functionsId.lower().replace('_', '-') log_filter.append('resource.labels.service_name="{}"'.format(service_name)) return ' '.join(log_filter) def _CreateLogFilter(args): # type: (parser_extensions.Namespace) -> str """Creates the filter for retrieving function logs based on the given args. Args: args: The arguments that were provided to this command invocation. Returns: """ function_ref = _GetFunctionRef(args.name) region = properties.VALUES.functions.region.GetOrFail() if flags.ShouldUseGen1(): log_filter = [_CreateGen1LogFilterBase(function_ref, region)] elif flags.ShouldUseGen2(): log_filter = [_CreateGen2LogFilterBase(function_ref, region)] else: log_filter = [ '({}) OR ({})'.format( _CreateGen1LogFilterBase(function_ref, region), _CreateGen2LogFilterBase(function_ref, region), ) ] # Append common filters if args.execution_id: log_filter.append('labels.execution_id="{}"'.format(args.execution_id)) if args.min_log_level: log_filter.append('severity>={}'.format(args.min_log_level.upper())) if args.end_time: log_filter.append( 'timestamp<="{}"'.format(logging_util.FormatTimestamp(args.end_time)) ) log_filter.append( 'timestamp>="{}"'.format( logging_util.FormatTimestamp( args.start_time or datetime.datetime.utcnow() - datetime.timedelta(days=7) ) ) ) return ' '.join(log_filter) def _YieldLogEntries(entries): """Processes the given entries to yield rows. Args: entries: the log entries to process. Yields: Rows with level, name, execution_id, time_utc, and log properties. """ for entry in entries: message = entry.textPayload if entry.jsonPayload: props = [ prop.value for prop in entry.jsonPayload.additionalProperties if prop.key == 'message' ] if len(props) == 1 and hasattr(props[0], 'string_value'): message = props[0].string_value row = {'log': message} if entry.severity: severity = six.text_type(entry.severity) if severity in flags.SEVERITIES: # Use short form (first letter) for expected severities. row['level'] = severity[0] else: # Print full form of unexpected severities. row['level'] = severity if entry.resource and entry.resource.labels: for label in entry.resource.labels.additionalProperties: if label.key in ['function_name', 'service_name']: row['name'] = label.value if entry.labels: for label in entry.labels.additionalProperties: if label.key == 'execution_id': row['execution_id'] = label.value if entry.timestamp: row['time_utc'] = util.FormatTimestamp(entry.timestamp) yield row @base.DefaultUniverseOnly @base.ReleaseTracks(base.ReleaseTrack.GA) class GetLogs(base.ListCommand): """Display log entries produced by Google Cloud Functions.""" @staticmethod def Args(parser): # type: (parser_extensions.ArgumentParser) -> None """Register flags for this command.""" flags.AddRegionFlag( parser, help_text='Only show logs generated by functions in the region.', ) base.LIMIT_FLAG.RemoveFromParser(parser) parser.add_argument( 'name', nargs='?', help=( 'Name of the function which logs are to be displayed. If no name ' 'is specified, logs from all functions are displayed.' ), ) parser.add_argument( '--execution-id', help='Execution ID for which logs are to be displayed.', ) parser.add_argument( '--start-time', required=False, type=arg_parsers.Datetime.Parse, help=( 'Return only log entries in which timestamps are not earlier ' 'than the specified time. If *--start-time* is not specified, a ' 'default start time of 1 week ago is assumed. See $ gcloud ' 'topic datetimes for information on time formats.' ), ) parser.add_argument( '--end-time', required=False, type=arg_parsers.Datetime.Parse, help=( 'Return only log entries which timestamps are not later than ' 'the specified time. If *--end-time* is specified but ' '*--start-time* is not, the command returns *--limit* latest ' 'log entries which appeared before --end-time. See ' '*$ gcloud topic datetimes* for information on time formats.' ), ) parser.add_argument( '--limit', required=False, type=arg_parsers.BoundedInt(1, 1000), default=20, help=( 'Number of log entries to be fetched; must not be greater than ' '1000. Note that the most recent entries in the specified time ' 'range are returned, rather than the earliest.' ), ) flags.AddMinLogLevelFlag(parser) parser.display_info.AddCacheUpdater(None) flags.AddGen2Flag(parser) @util_v1.CatchHTTPErrorRaiseHTTPException def Run(self, args): # type: (parser_extensions.Namespace) -> None """This is what gets called when the user runs this command. Args: args: an argparse namespace. All the arguments that were provided to this command invocation. Returns: A generator of objects representing log entries. """ if not args.IsSpecified('format'): args.format = _DEFAULT_TABLE_FORMAT log_filter = _CreateLogFilter(args) entries = list( logging_common.FetchLogs(log_filter, order_by='DESC', limit=args.limit) ) if args.name and not entries: client = client_v2.FunctionsClient(self.ReleaseTrack()) function_ref = _GetFunctionRef(args.name) if not client.GetFunction(function_ref.RelativeName()): # The function doesn't exist in the given region. log.warning( 'There is no function named `{}` in region `{}`. Perhaps you ' 'meant to specify `--region` or update the `functions/region` ' 'configuration property?'.format( function_ref.functionsId, function_ref.locationsId ) ) return _YieldLogEntries(entries) @base.ReleaseTracks(base.ReleaseTrack.BETA) class GetLogsBeta(GetLogs): """Display log entries produced by Google Cloud Functions.""" @base.ReleaseTracks(base.ReleaseTrack.ALPHA) class GetLogsAlpha(GetLogsBeta): """Display log entries produced by Google Cloud Functions."""