diff --git a/includes/API/MetaLog/Request.php b/includes/API/MetaLog/Request.php index 1bf03fd4c..e92933f99 100644 --- a/includes/API/MetaLog/Request.php +++ b/includes/API/MetaLog/Request.php @@ -8,7 +8,7 @@ defined( 'ABSPATH' ) || exit; /** - * Request object for MetaLog > Error Graph Api. + * Request object for MetaLog Graph Api. */ class Request extends ApiRequest { diff --git a/includes/API/MetaLog/Response.php b/includes/API/MetaLog/Response.php index 41f3f0065..7e6dc40c7 100644 --- a/includes/API/MetaLog/Response.php +++ b/includes/API/MetaLog/Response.php @@ -8,6 +8,6 @@ defined( 'ABSPATH' ) || exit; /** - * Response object for MetaLog > Error Graph Api. + * Response object for MetaLog Graph Api. */ class Response extends ApiResponse {} diff --git a/includes/Framework/BatchLogHandler.php b/includes/Framework/BatchLogHandler.php index dfd8e8e46..5f334b0d2 100644 --- a/includes/Framework/BatchLogHandler.php +++ b/includes/Framework/BatchLogHandler.php @@ -21,7 +21,7 @@ * * @since 3.5.0 */ -class BatchLogHandler { +class BatchLogHandler extends LogHandlerBase { /** * Constructs a new BatchLog handler. @@ -33,18 +33,58 @@ public function __construct() { } /** - * Function that runs every minute. + * Function that runs every five minutes. * * @internal * * @since 3.5.0 */ public function process_telemetry_logs_batch() { - if ( get_transient( 'global_telemetry_message_queue' ) !== false ) { - $logs = get_transient( 'global_telemetry_message_queue' ); + if ( get_transient( 'global_telemetry_message_queue' ) !== false && ! empty( get_transient( 'global_telemetry_message_queue' ) ) ) { + $logs = get_transient( 'global_telemetry_message_queue' ); + $chunked_logs = array_chunk( $logs, 20 ); - // TODO: Replace with send batch logging request to Meta function. - WC_Facebookcommerce_Utils::log( wp_json_encode( $logs ) ); + $chunked_failed_logs = array_map( + function ( $logs_chunk ) { + $logs_chunk_with_core_context = array_map( + function ( $log ) { + return self::set_core_log_context( $log ); + }, + $logs_chunk + ); + + $context = [ + 'event' => 'persist_meta_telemetry_logs', + 'extra_data' => [ 'telemetry_logs' => wp_json_encode( $logs_chunk_with_core_context ) ], + ]; + + try { + $response = facebook_for_woocommerce()->get_api()->log_to_meta( $context ); + if ( $response->success ) { + WC_Facebookcommerce_Utils::logWithDebugModeEnabled( 'Telemetry logs: ' . wp_json_encode( $context ) ); + return []; + } else { + WC_Facebookcommerce_Utils::logWithDebugModeEnabled( 'Bad response from log_to_meta request' ); + return $logs_chunk; + } + } catch ( \Exception $e ) { + WC_Facebookcommerce_Utils::logWithDebugModeEnabled( 'Error persisting telemetry logs: ' . $e->getMessage() ); + return $logs_chunk; + } + }, + $chunked_logs + ); + + $failed_logs = array_merge( ...$chunked_failed_logs ); + // Only keep the latest 100 failed logs, in case too much memory got eaten up on the host + if ( count( $failed_logs ) > 100 ) { + $failed_logs = array_slice( $failed_logs, -100 ); + } + + if ( ! empty( $failed_logs ) ) { + set_transient( 'global_telemetry_message_queue', $failed_logs, HOUR_IN_SECONDS ); + return; + } } set_transient( 'global_telemetry_message_queue', [], HOUR_IN_SECONDS ); diff --git a/includes/Framework/ErrorLogHandler.php b/includes/Framework/ErrorLogHandler.php index d92deaddc..4f4315c1f 100644 --- a/includes/Framework/ErrorLogHandler.php +++ b/includes/Framework/ErrorLogHandler.php @@ -10,6 +10,9 @@ namespace WooCommerce\Facebook\Framework; +use WC_Facebookcommerce_Utils; +use Throwable; + defined( 'ABSPATH' ) || exit; @@ -18,7 +21,7 @@ * * @since 3.5.0 */ -class ErrorLogHandler { +class ErrorLogHandler extends LogHandlerBase { /** * Hook name for Meta Log API. @@ -39,10 +42,50 @@ public function __construct() { * * @internal * - * @param array $context log context + * @param array $raw_context log context * @since 3.5.0 */ - public function process_error_log( $context ) { + public function process_error_log( $raw_context ) { + $context = self::set_core_log_context( $raw_context ); facebook_for_woocommerce()->get_api()->log_to_meta( $context ); + WC_Facebookcommerce_Utils::logWithDebugModeEnabled( 'Error log: ' . wp_json_encode( $context ) ); + } + + /** + * Utility function for sending exception logs to Meta. + * + * @since 3.5.0 + * + * @param Throwable $error error object + * @param array $context context example: ['catalog_id' => '1234567890', 'order_id' => '1234567890', + * 'promotion_id' => '1234567890', 'flow_name' => 'checkout', 'flow_step' => 'verification', + * 'extra_data' => ['dictionary type' => 'any data that is not fall into our pre-defined format.'] + */ + public static function log_exception_to_meta( Throwable $error, array $context = [] ) { + $extra_data = WC_Facebookcommerce_Utils::getContextData( $context, 'extra_data', [] ); + $extra_data['php_version'] = phpversion(); + + $request_data = [ + 'event' => 'error_log', + 'event_type' => WC_Facebookcommerce_Utils::getContextData( $context, 'event_type' ), + 'exception_message' => $error->getMessage(), + 'exception_trace' => $error->getTraceAsString(), + 'exception_code' => $error->getCode(), + 'exception_class' => get_class( $error ), + 'order_id' => WC_Facebookcommerce_Utils::getContextData( $context, 'order_id' ), + 'promotion_id' => WC_Facebookcommerce_Utils::getContextData( $context, 'promotion_id' ), + 'flow_name' => WC_Facebookcommerce_Utils::getContextData( $context, 'flow_name' ), + 'flow_step' => WC_Facebookcommerce_Utils::getContextData( $context, 'flow_step' ), + 'incoming_params' => WC_Facebookcommerce_Utils::getContextData( $context, 'incoming_params' ), + 'extra_data' => $extra_data, + ]; + + // Check if Action Scheduler is available + if ( function_exists( 'as_enqueue_async_action' ) ) { + as_enqueue_async_action( 'facebook_for_woocommerce_log_api', array( $request_data ) ); + } else { + // Handle the absence of the Action Scheduler + WC_Facebookcommerce_Utils::logWithDebugModeEnabled( 'Action Scheduler is not available.' ); + } } } diff --git a/includes/Framework/LogHandlerBase.php b/includes/Framework/LogHandlerBase.php new file mode 100644 index 000000000..d2783c9c3 --- /dev/null +++ b/includes/Framework/LogHandlerBase.php @@ -0,0 +1,48 @@ + facebook_for_woocommerce()->get_connection_handler()->get_commerce_merchant_settings_id(), + 'commerce_partner_integration_id' => facebook_for_woocommerce()->get_connection_handler()->get_commerce_partner_integration_id(), + 'external_business_id' => facebook_for_woocommerce()->get_connection_handler()->get_external_business_id(), + 'catalog_id' => facebook_for_woocommerce()->get_integration()->get_product_catalog_id(), + 'page_id' => facebook_for_woocommerce()->get_integration()->get_facebook_page_id(), + 'pixel_id' => facebook_for_woocommerce()->get_integration()->get_facebook_pixel_id(), + 'seller_platform_app_version' => self::PLUGIN_VERSION, + ]; + + return array_merge( $request_data, $context ); + } +} diff --git a/includes/Handlers/Connection.php b/includes/Handlers/Connection.php index 39a4e2114..95614ef23 100644 --- a/includes/Handlers/Connection.php +++ b/includes/Handlers/Connection.php @@ -97,6 +97,9 @@ class Connection { /** @var string the Commerce merchant settings ID option name */ const OPTION_COMMERCE_MERCHANT_SETTINGS_ID = 'wc_facebook_commerce_merchant_settings_id'; + /** @var string the Commerce Partner Integration ID option name */ + const OPTION_COMMERCE_PARTNER_INTEGRATION_ID = 'wc_facebook_commerce_partner_integration_id'; + /** @var string|null the generated external merchant settings ID */ private $external_business_id; @@ -794,6 +797,18 @@ public function get_commerce_merchant_settings_id() { } + /** + * Gets Commerce Partner Integration ID value. + * + * @since 3.5.0 + * + * @return string + */ + public function get_commerce_partner_integration_id() { + return get_option( self::OPTION_COMMERCE_PARTNER_INTEGRATION_ID, '' ); + } + + /** * Gets the proxy URL. * diff --git a/includes/fbutils.php b/includes/fbutils.php index 1368300ae..f2f33834d 100644 --- a/includes/fbutils.php +++ b/includes/fbutils.php @@ -14,6 +14,7 @@ use WooCommerce\Facebook\Events\AAMSettings; use WooCommerce\Facebook\Events\Normalizer; use WooCommerce\Facebook\Framework\Api\Exception as ApiException; +use WooCommerce\Facebook\Framework\ErrorLogHandler; use WooCommerce\Facebook\Products\Sync; if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) : @@ -868,39 +869,7 @@ public static function prepare_product_variation_data_items_batch( $product ) { * 'extra_data' => ['dictionary type' => 'any data that is not fall into our pre-defined format.'] */ public static function logExceptionImmediatelyToMeta(Throwable $error, array $context = []) { - $extra_data = self::getContextData($context, 'extra_data', []); - $extra_data['php_version'] = phpversion(); - - $request_data = [ - 'event' => 'error_log', - 'event_type' => self::getContextData($context, 'event_type'), - 'commerce_merchant_settings_id' => self::getContextData($context, 'commerce_merchant_settings_id', self::$ems), - 'commerce_partner_integration_id' => self::getContextData($context, 'commerce_partner_integration_id'), - 'exception_message' => $error->getMessage(), - 'exception_trace' => $error->getTraceAsString(), - 'exception_code' => $error->getCode(), - 'exception_class' => get_class($error), - 'external_business_id' => self::getContextData($context, 'external_business_id'), - 'catalog_id' => self::getContextData($context, 'catalog_id'), - 'order_id' => self::getContextData($context, 'order_id'), - 'page_id' => self::getContextData($context, 'page_id'), - 'promotion_id' => self::getContextData($context, 'promotion_id'), - 'flow_name' => self::getContextData($context, 'flow_name'), - 'flow_step' => self::getContextData($context, 'flow_step'), - 'incoming_params' => self::getContextData($context, 'incoming_params'), - 'seller_platform_app_version' => self::PLUGIN_VERSION, - 'extra_data' => $extra_data, - ]; - - // Check if Action Scheduler is available - if ( function_exists( 'as_enqueue_async_action' ) ) { - as_enqueue_async_action( 'facebook_for_woocommerce_log_api', array( $request_data ) ); - } else { - // Handle the absence of the Action Scheduler - self::logWithDebugModeEnabled( 'Action Scheduler is not available.' ); - } - - self::logWithDebugModeEnabled( 'Request data: ' . json_encode( $request_data ) ); + ErrorLogHandler::log_exception_to_meta($error, $context); } /** @@ -908,14 +877,11 @@ public static function logExceptionImmediatelyToMeta(Throwable $error, array $co * @since 3.5.0 */ public static function logTelemetryToMeta(string $message, array $context = []) { - /** - * WIP: This is a dummy function to send telemetry logs to Meta. - * $context is an array of data that will be sent to Meta, includes commerce_merchant_settings_id, - * catalog_id, order_id, promotion_id, flow_name, flow_step, extra_data and etc. - */ - + $extra_data = self::getContextData( $context, 'extra_data', [] ); + $extra_data['message'] = $message; + $context['extra_data'] = $extra_data; + // Push logging request to global message queue function. - $context['extra_data'] = ['message' => $message]; $logs = get_transient( 'global_telemetry_message_queue' ); $logs[] = $context; set_transient( 'global_telemetry_message_queue', $logs, HOUR_IN_SECONDS ); @@ -949,7 +915,7 @@ public static function is_fpassthru_disabled(): bool { * @param mixed $default * @return mixed */ - private static function getContextData(array $context, string $key, $default = null) + public static function getContextData(array $context, string $key, $default = null) { return $context[$key] ?? $default; }