1# -*- coding: utf-8 -*- # 2# Copyright 2015 Google LLC. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""Displays log entries produced by Google Cloud Functions.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import unicode_literals 20 21import datetime 22from apitools.base.py.exceptions import HttpForbiddenError 23from apitools.base.py.exceptions import HttpNotFoundError 24from googlecloudsdk.api_lib.functions.v1 import util 25from googlecloudsdk.api_lib.logging import common as logging_common 26from googlecloudsdk.api_lib.logging import util as logging_util 27from googlecloudsdk.calliope import arg_parsers 28from googlecloudsdk.calliope import base 29from googlecloudsdk.command_lib.functions import flags 30from googlecloudsdk.core import log 31from googlecloudsdk.core import properties 32import six 33 34 35class GetLogs(base.ListCommand): 36 """Display log entries produced by Google Cloud Functions.""" 37 38 @staticmethod 39 def Args(parser): 40 """Register flags for this command.""" 41 flags.AddRegionFlag( 42 parser, 43 help_text='Only show logs generated by functions in the region.', 44 ) 45 base.LIMIT_FLAG.RemoveFromParser(parser) 46 parser.add_argument( 47 'name', 48 nargs='?', 49 help=('Name of the function which logs are to be displayed. If no name ' 50 'is specified, logs from all functions are displayed.')) 51 parser.add_argument( 52 '--execution-id', 53 help=('Execution ID for which logs are to be displayed.')) 54 parser.add_argument( 55 '--start-time', 56 required=False, 57 type=arg_parsers.Datetime.Parse, 58 help=('Return only log entries in which timestamps are not earlier ' 59 'than the specified time. If *--start-time* is not specified, a ' 60 'default start time of 1 week ago is assumed. See $ gcloud ' 61 'topic datetimes for information on time formats.')) 62 parser.add_argument( 63 '--end-time', 64 required=False, 65 type=arg_parsers.Datetime.Parse, 66 help=('Return only log entries which timestamps are not later than ' 67 'the specified time. If *--end-time* is specified but ' 68 '*--start-time* is not, the command returns *--limit* latest ' 69 'log entries which appeared before --end-time. See ' 70 '*$ gcloud topic datetimes* for information on time formats.')) 71 parser.add_argument( 72 '--limit', 73 required=False, 74 type=arg_parsers.BoundedInt(1, 1000), 75 default=20, 76 help=('Number of log entries to be fetched; must not be greater than ' 77 '1000.')) 78 flags.AddMinLogLevelFlag(parser) 79 parser.display_info.AddCacheUpdater(None) 80 81 @util.CatchHTTPErrorRaiseHTTPException 82 def Run(self, args): 83 """This is what gets called when the user runs this command. 84 85 Args: 86 args: an argparse namespace. All the arguments that were provided to this 87 command invocation. 88 89 Returns: 90 A generator of objects representing log entries. 91 """ 92 if not args.IsSpecified('format'): 93 args.format = self._Format(args) 94 95 return self._Run(args) 96 97 def _Run(self, args): 98 region = properties.VALUES.functions.region.Get() 99 log_filter = [ 100 'resource.type="cloud_function"', 101 'resource.labels.region="%s"' % region, 'logName:"cloud-functions"' 102 ] 103 104 if args.name: 105 log_filter.append('resource.labels.function_name="%s"' % args.name) 106 if args.execution_id: 107 log_filter.append('labels.execution_id="%s"' % args.execution_id) 108 if args.min_log_level: 109 log_filter.append('severity>=%s' % args.min_log_level.upper()) 110 111 log_filter.append('timestamp>="%s"' % logging_util.FormatTimestamp( 112 args.start_time or 113 datetime.datetime.utcnow() - datetime.timedelta(days=7))) 114 115 if args.end_time: 116 log_filter.append('timestamp<="%s"' % 117 logging_util.FormatTimestamp(args.end_time)) 118 119 log_filter = ' '.join(log_filter) 120 121 entries = list( 122 logging_common.FetchLogs(log_filter, order_by='ASC', limit=args.limit)) 123 124 if args.name and not entries: 125 # Check if the function even exists in the given region. 126 try: 127 client = util.GetApiClientInstance() 128 messages = client.MESSAGES_MODULE 129 client.projects_locations_functions.Get( 130 messages.CloudfunctionsProjectsLocationsFunctionsGetRequest( 131 name='projects/%s/locations/%s/functions/%s' % 132 (properties.VALUES.core.project.Get(required=True), region, 133 args.name))) 134 except (HttpForbiddenError, HttpNotFoundError): 135 # The function doesn't exist in the given region. 136 log.warning( 137 'There is no function named `%s` in region `%s`. Perhaps you ' 138 'meant to specify `--region` or update the `functions/region` ' 139 'configuration property?' % (args.name, region)) 140 141 for entry in entries: 142 message = entry.textPayload 143 if entry.jsonPayload: 144 props = [ 145 prop.value 146 for prop in entry.jsonPayload.additionalProperties 147 if prop.key == 'message' 148 ] 149 if len(props) == 1 and hasattr(props[0], 'string_value'): 150 message = props[0].string_value 151 row = {'log': message} 152 if entry.severity: 153 severity = six.text_type(entry.severity) 154 if severity in flags.SEVERITIES: 155 # Use short form (first letter) for expected severities. 156 row['level'] = severity[0] 157 else: 158 # Print full form of unexpected severities. 159 row['level'] = severity 160 if entry.resource and entry.resource.labels: 161 for label in entry.resource.labels.additionalProperties: 162 if label.key == 'function_name': 163 row['name'] = label.value 164 if entry.labels: 165 for label in entry.labels.additionalProperties: 166 if label.key == 'execution_id': 167 row['execution_id'] = label.value 168 if entry.timestamp: 169 row['time_utc'] = util.FormatTimestamp(entry.timestamp) 170 yield row 171 172 def _Format(self, args): 173 return 'table(level,name,execution_id,time_utc,log)' 174