Change Event provides a detailed breakdown of what changed in your account. When reporting on a create operation, all new field values are returned; and when reporting on an update operation, both new and old values are returned, giving you a complete picture of individual changes.
Additionally, the client type (such as API or web client) that was used to make the change is indicated, as well as which user made the change, assuming that information is also visible in the Change History of the web client.
Change Event types
See the list of resources types for ChangeEventResourceType
.
Query
The list of changes must be filtered by date. The date range must be within the past 30 days.
The query must also include a LIMIT
clause restricting results to at most
10,000 rows. If you're interested in more than 10,000 results, make a note of
the timestamp of the last entry in the returned result set, and then set your
date range on the following query to start after that time to continue where you
left off.
A change can take up to three minutes to be reflected in the change event results.
The following example demonstrates how to fetch change events and process them, including determining who made the change, which client type they used to make the change, and the old and new values of each field in the change:
Java
private void runExample ( GoogleAdsClient googleAdsClient , long customerId ) { // Defines a GAQL query to retrieve change_event instances from the last 14 days. String query = String . format ( "SELECT" + " change_event.resource_name," + " change_event.change_date_time," + " change_event.change_resource_name," + " change_event.user_email," + " change_event.client_type," + " change_event.change_resource_type," + " change_event.old_resource," + " change_event.new_resource," + " change_event.resource_change_operation," + " change_event.changed_fields " + "FROM " + " change_event " + "WHERE " + " change_event.change_date_time <= '%s' " + " AND change_event.change_date_time >= '%s' " + "ORDER BY" + " change_event.change_date_time DESC " + "LIMIT 5" , LocalDate . now (). toString ( "YYYY-MM-dd" ), LocalDate . now (). minusDays ( 14 ). toString ( "YYYY-MM-dd" )); // Creates a GoogleAdsServiceClient instance. try ( GoogleAdsServiceClient client = googleAdsClient . getLatestVersion (). createGoogleAdsServiceClient ()) { // Issues the search query. SearchPagedResponse response = client . search ( String . valueOf ( customerId ), query ); // Processes the rows of the response. for ( GoogleAdsRow row : response . iterateAll ()) { ChangeEvent event = row . getChangeEvent (); // Prints some general information about the change event. System . out . printf ( "On '%s', user '%s' used interface '%s' to perform a(n) '%s' operation on a '%s' with" + " resource name '%s'.%n" , event . getChangeDateTime (), event . getUserEmail (), event . getClientType (), event . getResourceChangeOperation (), event . getChangeResourceType (), event . getResourceName ()); // Prints some detailed information about update and create operations. if ( event . getResourceChangeOperation () == ResourceChangeOperation . UPDATE || event . getResourceChangeOperation () == ResourceChangeOperation . CREATE ) { // Retrieves the entity that was changed. Optional<Message> oldResource = getResourceByType ( event . getOldResource (), event . getChangeResourceType ()); Optional<Message> newResource = getResourceByType ( event . getNewResource (), event . getChangeResourceType ()); // Prints the old and new values for each field that was updated/created. for ( String changedPath : row . getChangeEvent (). getChangedFields (). getPathsList ()) { // Uses the FieldMasks utility to retrieve a value from a . delimited path. List < ? extends Object > oldValue = oldResource . isPresent () ? FieldMasks . getFieldValue ( changedPath , oldResource . get ()) : Collections . emptyList (); List < ? extends Object > newValue = newResource . isPresent () ? FieldMasks . getFieldValue ( changedPath , newResource . get ()) : Collections . emptyList (); // Prints different messages for UPDATE and CREATE cases. if ( event . getResourceChangeOperation () == ResourceChangeOperation . UPDATE ) { System . out . printf ( "\t %s changed from %s to %s.%n" , changedPath , oldValue , newValue ); } else if ( event . getResourceChangeOperation () == ResourceChangeOperation . CREATE ) { System . out . printf ( "\t %s set to %s.%n" , changedPath , newValue ); } } } } } }
C#
public void Run ( GoogleAdsClient client , long customerId ) { // Get the GoogleAdsService. GoogleAdsServiceClient googleAdsService = client . GetService ( Services . V21 . GoogleAdsService ); // Construct a query to find details for recent changes in your account. // The LIMIT clause is required for the change_event resource. // The maximum size is 10000, but a low limit was set here for demonstrative // purposes. // The WHERE clause on change_date_time is also required. It must specify a // window of at most 30 days within the past 30 days. string startDate = DateTime . Today . Subtract ( TimeSpan . FromDays ( 25 )). ToString ( "yyyyMMdd" ); string endDate = DateTime . Today . Add ( TimeSpan . FromDays ( 1 )). ToString ( "yyyyMMdd" ); string searchQuery = $ @" SELECT change_event.resource_name, change_event.change_date_time, change_event.change_resource_name, change_event.user_email, change_event.client_type, change_event.change_resource_type, change_event.old_resource, change_event.new_resource, change_event.resource_change_operation, change_event.changed_fields FROM change_event WHERE change_event.change_date_time >= '{startDate}' AND change_event.change_date_time <= '{endDate}' ORDER BY change_event.change_date_time DESC LIMIT 5" ; try { // Issue a search request. googleAdsService . SearchStream ( customerId . ToString (), searchQuery , delegate ( SearchGoogleAdsStreamResponse resp ) { // Display the results. foreach ( GoogleAdsRow googleAdsRow in resp . Results ) { ChangeEvent changeEvent = googleAdsRow . ChangeEvent ; ChangedResource oldResource = changeEvent . OldResource ; ChangedResource newResource = changeEvent . NewResource ; bool knownResourceType = true ; IMessage oldResourceEntity = null ; IMessage newResourceEntity = null ; switch ( changeEvent . ChangeResourceType ) { case ChangeEventResourceType . Ad : oldResourceEntity = oldResource . Ad ; newResourceEntity = newResource . Ad ; break ; case ChangeEventResourceType . AdGroup : oldResourceEntity = oldResource . AdGroup ; newResourceEntity = newResource . AdGroup ; break ; case ChangeEventResourceType . AdGroupAd : oldResourceEntity = oldResource . AdGroupAd ; newResourceEntity = newResource . AdGroupAd ; break ; case ChangeEventResourceType . AdGroupAsset : oldResourceEntity = oldResource . AdGroupAsset ; newResourceEntity = newResource . AdGroupAsset ; break ; case ChangeEventResourceType . AdGroupBidModifier : oldResourceEntity = oldResource . AdGroupBidModifier ; newResourceEntity = newResource . AdGroupBidModifier ; break ; case ChangeEventResourceType . AdGroupCriterion : oldResourceEntity = oldResource . AdGroupCriterion ; newResourceEntity = newResource . AdGroupCriterion ; break ; case ChangeEventResourceType . Asset : oldResourceEntity = oldResource . Asset ; newResourceEntity = newResource . Asset ; break ; case ChangeEventResourceType . AssetSet : oldResourceEntity = oldResource . AssetSet ; newResourceEntity = newResource . AssetSet ; break ; case ChangeEventResourceType . AssetSetAsset : oldResourceEntity = oldResource . AssetSetAsset ; newResourceEntity = newResource . AssetSetAsset ; break ; case ChangeEventResourceType . Campaign : oldResourceEntity = oldResource . Campaign ; newResourceEntity = newResource . Campaign ; break ; case ChangeEventResourceType . CampaignAsset : oldResourceEntity = oldResource . CampaignAsset ; newResourceEntity = newResource . CampaignAsset ; break ; case ChangeEventResourceType . CampaignAssetSet : oldResourceEntity = oldResource . CampaignAssetSet ; newResourceEntity = newResource . CampaignAssetSet ; break ; case ChangeEventResourceType . CampaignBudget : oldResourceEntity = oldResource . CampaignBudget ; newResourceEntity = newResource . CampaignBudget ; break ; case ChangeEventResourceType . CampaignCriterion : oldResourceEntity = oldResource . CampaignCriterion ; newResourceEntity = newResource . CampaignCriterion ; break ; case ChangeEventResourceType . CustomerAsset : oldResourceEntity = oldResource . CustomerAsset ; newResourceEntity = newResource . CustomerAsset ; break ; default : knownResourceType = false ; break ; } if ( ! knownResourceType ) { Console . WriteLine ( $"Unknown change_resource_type " + $"'{changeEvent.ChangeResourceType}'." ); continue ; } Console . WriteLine ( $"On #{changeEvent.ChangeDateTime}, user " + $"{changeEvent.UserEmail} used interface {changeEvent.ClientType} " + $"to perform a(n) '{changeEvent.ResourceChangeOperation}' " + $"operation on a '{changeEvent.ChangeResourceType}' with " + $"resource name {changeEvent.ChangeResourceName}." ); foreach ( string fieldMaskPath in changeEvent . ChangedFields . Paths ) { if ( changeEvent . ResourceChangeOperation == ResourceChangeOperation . Create ) { object newValue = FieldMasks . GetFieldValue ( fieldMaskPath , newResourceEntity ); Console . WriteLine ( $"\t{fieldMaskPath} set to '{newValue}'." ); } else if ( changeEvent . ResourceChangeOperation == ResourceChangeOperation . Update ) { object oldValue = FieldMasks . GetFieldValue ( fieldMaskPath , oldResourceEntity ); object newValue = FieldMasks . GetFieldValue ( fieldMaskPath , newResourceEntity ); Console . WriteLine ( $"\t{fieldMaskPath} changed from " + $"'{oldValue}' to '{newValue}'." ); } } } }); } catch ( GoogleAdsException e ) { Console . WriteLine ( "Failure:" ); Console . WriteLine ( $"Message: {e.Message}" ); Console . WriteLine ( $"Failure: {e.Failure}" ); Console . WriteLine ( $"Request ID: {e.RequestId}" ); throw ; } }
PHP
public static function runExample(GoogleAdsClient $googleAdsClient, int $customerId) { $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); // Constructs a query to find details for recent changes in your account. // The LIMIT clause is required for the change_event resource. // The maximum size is 10000, but a low limit was set here for demonstrative // purposes. // The WHERE clause on change_date_time is also required. It must specify a // window of at most 30 days within the past 30 days. $query = 'SELECT change_event.resource_name, ' . 'change_event.change_date_time, ' . 'change_event.change_resource_name, ' . 'change_event.user_email, ' . 'change_event.client_type, ' . 'change_event.change_resource_type, ' . 'change_event.old_resource, ' . 'change_event.new_resource, ' . 'change_event.resource_change_operation, ' . 'change_event.changed_fields ' . 'FROM change_event ' . sprintf( 'WHERE change_event.change_date_time <= %s ', date_format(new DateTime('+1 day'), 'Ymd') ) . sprintf( 'AND change_event.change_date_time >= %s ', date_format(new DateTime('-14 days'), 'Ymd') ) . 'ORDER BY change_event.change_date_time DESC ' . 'LIMIT 5'; // Issues a search request. $response = $googleAdsServiceClient->search(SearchGoogleAdsRequest::build($customerId, $query)); // Iterates over all rows in all pages and prints the requested field values for // the change event in each row. foreach ($response->iterateAllElements() as $googleAdsRow) { /** @var GoogleAdsRow $googleAdsRow */ $changeEvent = $googleAdsRow->getChangeEvent(); $oldResource = $changeEvent->getOldResource(); $newResource = $changeEvent->getNewResource(); $isResourceTypeKnown = true; $oldResourceEntity = null; $newResourceEntity = null; switch ($changeEvent->getChangeResourceType()) { case ChangeEventResourceType::AD: $oldResourceEntity = $oldResource->getAd(); $newResourceEntity = $newResource->getAd(); break; case ChangeEventResourceType::AD_GROUP: $oldResourceEntity = $oldResource->getAdGroup(); $newResourceEntity = $newResource->getAdGroup(); break; case ChangeEventResourceType::AD_GROUP_AD: $oldResourceEntity = $oldResource->getAdGroupAd(); $newResourceEntity = $newResource->getAdGroupAd(); break; case ChangeEventResourceType::AD_GROUP_ASSET: $oldResourceEntity = $oldResource->getAdGroupAsset(); $newResourceEntity = $newResource->getAdGroupAsset(); break; case ChangeEventResourceType::AD_GROUP_CRITERION: $oldResourceEntity = $oldResource->getAdGroupCriterion(); $newResourceEntity = $newResource->getAdGroupCriterion(); break; case ChangeEventResourceType::AD_GROUP_BID_MODIFIER: $oldResourceEntity = $oldResource->getAdGroupBidModifier(); $newResourceEntity = $newResource->getAdGroupBidModifier(); break; case ChangeEventResourceType::ASSET: $oldResourceEntity = $oldResource->getAsset(); $newResourceEntity = $newResource->getAsset(); break; case ChangeEventResourceType::ASSET_SET: $oldResourceEntity = $oldResource->getAssetSet(); $newResourceEntity = $newResource->getAssetSet(); break; case ChangeEventResourceType::ASSET_SET_ASSET: $oldResourceEntity = $oldResource->getAssetSetAsset(); $newResourceEntity = $newResource->getAssetSetAsset(); break; case ChangeEventResourceType::CAMPAIGN: $oldResourceEntity = $oldResource->getCampaign(); $newResourceEntity = $newResource->getCampaign(); break; case ChangeEventResourceType::CAMPAIGN_ASSET: $oldResourceEntity = $oldResource->getCampaignAsset(); $newResourceEntity = $newResource->getCampaignAsset(); break; case ChangeEventResourceType::CAMPAIGN_ASSET_SET: $oldResourceEntity = $oldResource->getCampaignAssetSet(); $newResourceEntity = $newResource->getCampaignAssetSet(); break; case ChangeEventResourceType::CAMPAIGN_BUDGET: $oldResourceEntity = $oldResource->getCampaignBudget(); $newResourceEntity = $newResource->getCampaignBudget(); break; case ChangeEventResourceType::CAMPAIGN_CRITERION: $oldResourceEntity = $oldResource->getCampaignCriterion(); $newResourceEntity = $newResource->getCampaignCriterion(); break; case ChangeEventResourceType::CUSTOMER_ASSET: $oldResourceEntity = $oldResource->getCustomerAsset(); $newResourceEntity = $newResource->getCustomerAsset(); break; default: $isResourceTypeKnown = false; break; } if (!$isResourceTypeKnown) { printf( "Unknown change_resource_type %s.%s", ChangeEventResourceType::name($changeEvent->getChangeResourceType()), PHP_EOL ); } $resourceChangeOperation = $changeEvent->getResourceChangeOperation(); printf( "On %s, user '%s' used interface '%s' to perform a(n) '%s' operation on a '%s' " . "with resource name '%s'.%s", $changeEvent->getChangeDateTime(), $changeEvent->getUserEmail(), ChangeClientType::name($changeEvent->getClientType()), ResourceChangeOperation::name($resourceChangeOperation), ChangeEventResourceType::name($changeEvent->getChangeResourceType()), $changeEvent->getChangeResourceName(), PHP_EOL ); if ( $resourceChangeOperation !== ResourceChangeOperation::CREATE && $resourceChangeOperation !== ResourceChangeOperation::UPDATE ) { continue; } foreach ($changeEvent->getChangedFields()->getPaths() as $path) { $newValueStr = self::convertToString( FieldMasks::getFieldValue($path, $newResourceEntity, true) ); if ($resourceChangeOperation === ResourceChangeOperation::CREATE) { printf("\t'$path' set to '%s'.%s", $newValueStr, PHP_EOL); } elseif ($resourceChangeOperation === ResourceChangeOperation::UPDATE) { printf( "\t'$path' changed from '%s' to '%s'.%s", self::convertToString( FieldMasks::getFieldValue($path, $oldResourceEntity, true) ), $newValueStr, PHP_EOL ); } } } } /** * Converts the specified value to string. * * @param mixed $value the value to be converted to string * @return string the value in string */ private static function convertToString($value) { if (is_null($value)) { return 'no value'; } if (gettype($value) === 'boolean') { return $value ? 'true' : 'false'; } elseif (gettype($value) === 'object') { if (get_class($value) === RepeatedField::class) { $strValues = []; foreach (iterator_to_array($value->getIterator()) as $element) { /** @type Message $element */ $strValues[] = $element->serializeToJsonString(); } return '[' . implode(',', $strValues) . ']'; } return json_encode($value); } else { return strval($value); } }