הסבר על שגיאות API

במדריך הזה מוסבר איך Data Manager API מטפל בשגיאות ואיך הוא מעביר אותן. הבנת המבנה והמשמעות של שגיאות ב-API היא חיונית לבניית אפליקציות חזקות שיכולות לטפל בבעיות בצורה חלקה, החל מקלט לא תקין ועד לזמינות זמנית של שירות.

‫Data Manager API פועל לפי מודל השגיאות הרגיל של Google API, שמבוסס על קודי סטטוס של gRPC. כל תגובה מה-API שמובילה לשגיאה כוללת אובייקט Status עם:

  • קוד שגיאה מספרי.
  • הודעת שגיאה.
  • פרטי שגיאה נוספים (אופציונלי).

קודי שגיאה קנוניים

ב-Data Manager API נעשה שימוש בקבוצה של קודי שגיאה קנוניים שמוגדרים על ידי gRPC ו-HTTP. הקודים האלה מציינים באופן כללי את סוג השגיאה. תמיד כדאי לבדוק קודם את הקוד הזה כדי להבין את מהות הבעיה.

פרטים נוספים על הקודים האלה זמינים במאמר מדריך לעיצוב API – קודי שגיאה.

מודל של כשל מהיר

ה-Data Manager API משתמש במודל של כשל מהיר. אם בקשה מכילה שגיאות מבניות או אם אימות של רשומה כלשהי נכשל בגלל שדה חובה, הבקשה כולה נכשלת וה-API לא מעבד אף אחד מהנתונים בבקשה.

מודל הכשל המהיר שונה ממודל הכשל החלקי בממשקי API אחרים של Google, כמו Google Ads API ו-Campaign Manager 360 API. במודל של כשל חלקי, הבקשה מצליחה גם אם יש שגיאות בחלק מהרשומות, והתשובה מכילה פרטים על השגיאות ברשומות שנכשלו.

למרות שהמודל של כשל חלקי יכול להיות נוח, הוא כרוך בסיכונים משמעותיים כי הוא לא מתריע באופן יזום על שגיאות – אתם צריכים לבדוק במפורש אם יש שגיאות בכל תשובה. הבעיה היא שאפשר להסתיר בעיות חשובות, כי הבקשה מצליחה גם אם ה-API דוחה הרבה רשומות בבקשה, או אפילו את כולן. אם חלק משמעותי מהרשומות בבקשה מכיל שגיאות, אבל אתם לא בודקים את התגובה, יכול להיות שלא תדעו על בעיות נרחבות בנתונים שלכם, ותגלו אותן רק ימים או שבועות לאחר מכן, כשהתוצאות המצטברות לא יתאימו לציפיות שלכם.

מודל הכשל המהיר מאפשר לכם להימנע מהבעיות האלה כי הוא מתריע על בעיות בנתונים או בשילוב באופן מיידי, כדי שתוכלו לפעול בהתאם.

טיפול בשגיאות

אם הבקשה נכשלת, פועלים לפי השלבים הבאים:

  1. כדי לגלות את סוג השגיאה, בודקים את קוד השגיאה.

    • אם משתמשים ב-gRPC, קוד השגיאה נמצא בשדה code של Status. אם משתמשים בספריית לקוח, יכול להיות שהיא תזרוק סוג ספציפי של חריגה שמתאים לקוד השגיאה. לדוגמה, ספריית הלקוח ל-Java מחזירה את השגיאה com.google.api.gax.rpc.InvalidArgumentException אם קוד השגיאה הוא INVALID_ARGUMENT.
    • אם משתמשים ב-REST, קוד השגיאה מופיע בתגובת השגיאה בכתובת error.status, וסטטוס ה-HTTP המתאים מופיע בכתובת error.code.
  2. בודקים את מטען הנתונים של הפרטים הרגילים כדי למצוא את קוד השגיאה. מטעני הנתונים של הפרטים הרגילים הם קבוצה של הודעות לגבי שגיאות מ-Google APIs. הן מספקות פרטים על השגיאות בצורה מובנית ועקבית. לכל שגיאה מ-Data Manager API יכולות להיות כמה הודעות מטען ייעודי (payload) עם פרטים רגילים. בספריות הלקוח של Data Manager API יש שיטות עזר לקבלת מטען ייעודי (payload) של פרטים רגילים משגיאה.

    לא משנה מה קוד השגיאה, מומלץ לבדוק את מטען הנתונים (payload) של ErrorInfo,‏ RequestInfo,‏ Help ושל LocalizedMessage ולתעד אותו.

    • ‫ErrorInfo מכיל מידע שאולי לא מופיע במטען ייעודי אחר.
    • RequestInfo כולל את מזהה הבקשה, שיכול לעזור לכם אם תצטרכו לפנות לתמיכה.
    • ‫Help ו-LocalizedMessage מכילים קישורים ופרטים אחרים שיעזרו לך לפתור את השגיאה.

    בנוסף, מטען הנתונים BadRequest שימושי לשגיאות INVALID_ARGUMENT כי הוא מספק מידע על השדות שגרמו לשגיאה.

אזהרות לגבי הטמעה

‫Data Manager API מקבל כמה שיותר בקשות להטמעת נתונים. אם תכללו נתונים שלא נדרשים, בקשת האימות של השדות האלה לא תיכשל. לדוגמה, אם פריט בעגלת הקניות לא כולל מזהה מוצר של המוכר, ה-API מעבד את שאר הבקשה ומחזיר אזהרה.

תשובה מוצלחת על הטמעה (קוד סטטוס של HTTP‏ 200) כוללת את האזהרות האלה ברשימה field_warnings. כל רשומה היא אובייקט FieldWarning עם השדות הבאים:

field

המיקום של השדה בבקשה, בתחביר של נתיב בפורמט snake case.

אם נתיב מצביע על פריט ברשימה (שדה repeated), האינדקס שלו מוצג בסוגריים מרובעים ([...]) אחרי שם הרשימה.

לדוגמה, events.events[0].cart_data.items[0].merchant_product_id מציין אזהרה שקשורה לפריט הראשון בנתוני עגלת הקניות של האירוע הראשון בבקשה.

description

הסבר למה הערך שצוין גרם להצגת אזהרה.

reason

ערך ה-enum‏ WarningReason שמזהה את סוג האזהרה.

דוגמה עם FieldWarning

זו דוגמה לתגובה לבקשת הטמעה מוצלחת שמכילה אזהרה כי חסר מזהה מוצר של מוֹכר לאחד מהפריטים בעגלת הקניות.

{
  "requestId": "126365e1-16d0-4c81-9de9-f362711e250a",
  "fieldWarnings": [
    {
      "field": "events.events[0].cart_data.items[0].merchant_product_id",
      "description": "The merchant product ID is missing in the cart item.",
      "reason": "WARNING_REASON_CART_DATA_ITEM_MERCHANT_PRODUCT_ID_MISSING"
    }
  ]
}

מטענים סטנדרטיים של פרטים

אלה המטענים הייעודיים (payloads) הנפוצים ביותר של פרטים סטנדרטיים ב-Data Manager API:

BadRequest

אם בקשה נכשלת עם INVALID_ARGUMENT (קוד סטטוס של HTTP‏ 400), צריך לבדוק את מטען הייעודי (payload) של BadRequest.

ההודעה BadRequest מציינת שבבקשה היו שדות עם ערכים שגויים, או שחסר ערך בשדה חובה. בודקים את הרשימה field_violations בBadRequest כדי לראות באילו שדות יש שגיאות. כל רשומה field_violations כוללת מידע שיעזור לכם לתקן את השגיאה:

field

המיקום של השדה בבקשה, בתחביר של נתיב בפורמט snake case.

אם נתיב מצביע על פריט ברשימה (שדה repeated), האינדקס שלו מוצג בסוגריים מרובעים ([...]) אחרי שם הרשימה.

לדוגמה, destinations[0].operating_account.account_id הוא account_id ב-operating_account של הפריט הראשון ברשימה destinations.

description

הסבר למה הערך גרם לשגיאה.

reason

הספירה ErrorReason, לדוגמה INVALID_HEX_ENCODING או INVALID_CURRENCY_CODE.

דוגמאות של BadRequest

זוהי דוגמה לתשובה לשגיאה INVALID_ARGUMENT עם הודעה BadRequest: השגיאה field_violations מציינת ש-accountId הוא לא מספר. הערך field destinations[0].login_account.account_id מראה שהשדה accountId עם הפרה נמצא בlogin_account של הפריט הראשון ברשימה destinations.

{
  "error": {
    "code": 400,
    "message": "There was a problem with the request.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "INVALID_ARGUMENT",
        "domain": "datamanager.googleapis.com",
        "metadata": {
          "requestId": "t-a8896317-069f-4198-afed-182a3872a660"
        }
      },
      {
        "@type": "type.googleapis.com/google.rpc.RequestInfo",
        "requestId": "t-a8896317-069f-4198-afed-182a3872a660"
      },
      {
        "@type": "type.googleapis.com/google.rpc.BadRequest",
        "fieldViolations": [
          {
            "field": "destinations[0].login_account.account_id",
            "description": "String is not a valid number.",
            "reason": "INVALID_NUMBER_FORMAT"
          }
        ]
      }
    ]
  }
}

הנה דוגמה נוספת לתשובה משגיאת INVALID_ARGUMENT עם הודעה BadRequest. במקרה הזה, ברשימה field_violations מוצגות שתי שגיאות:

  1. לפרמטר הראשון event יש ערך שלא מקודד בפורמט הקסדצימלי במזהה המשתמש השני של האירוע.

  2. ל-event השני יש ערך שלא מקודד בפורמט הקסדצימלי במזהה המשתמש השלישי של האירוע.

{
  "error": {
    "code": 400,
    "message": "There was a problem with the request.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "INVALID_ARGUMENT",
        "domain": "datamanager.googleapis.com",
        "metadata": {
          "requestId": "t-6bc8fb83-d648-4942-9c49-2604276638d8"
        }
      },
      {
        "@type": "type.googleapis.com/google.rpc.RequestInfo",
        "requestId": "t-6bc8fb83-d648-4942-9c49-2604276638d8"
      },
      {
        "@type": "type.googleapis.com/google.rpc.BadRequest",
        "fieldViolations": [
          {
            "field": "events.events[0].user_data.user_identifiers[1]",
            "description": "The HEX encoded value is malformed.",
            "reason": "INVALID_HEX_ENCODING"
          },
          {
            "field": "events.events[1].user_data.user_identifiers[2]",
            "description": "The HEX encoded value is malformed.",
            "reason": "INVALID_HEX_ENCODING"
          }
        ]
      }
    ]
  }
}

RequestInfo

בכל פעם שבקשה נכשלת, בודקים את מטען הייעודי (payload) של RequestInfo. ‫RequestInfo כולל את request_id שמזהה באופן ייחודי את בקשת ה-API.

{
  "@type": "type.googleapis.com/google.rpc.RequestInfo",
  "requestId": "t-4490c640-dc5d-4c28-91c1-04a1cae0f49f"
}

כשרושמים שגיאות ביומן או פונים לתמיכה, חשוב לכלול את מזהה הבקשה כדי לאבחן בעיות.

ErrorInfo

כדאי לבדוק אם מופיעה ההודעה ErrorInfo כדי לאחזר מידע נוסף שאולי לא נכלל במטענים הייעודיים (payloads) האחרים של הפרטים הסטנדרטיים. המטען הייעודי ErrorInfo(Payload) מכיל מפת metadata עם מידע על השגיאה.

לדוגמה, כאן מופיע ErrorInfo של כשל PERMISSION_DENIED שנגרם כתוצאה משימוש בפרטי כניסה לפרויקט בענן של Google Cloud שבו Data Manager API לא מופעל. בErrorInfo מופיע מידע נוסף על השגיאה, כמו:

  • הפרויקט שמשויך לבקשה, בקטע metadata.consumer.
  • שם השירות, בקטע metadata.serviceTitle.
  • כתובת ה-URL שבה אפשר להפעיל את השירות, בקטע metadata.activationUrl.
{
  "error": {
    "code": 403,
    "message": "Data Manager API has not been used in project PROJECT_NUMBER before or it is disabled. Enable it by visiting https://console.cloud.google.com/apis/api/datamanager.googleapis.com/overview?project=PROJECT_NUMBER then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "SERVICE_DISABLED",
        "domain": "googleapis.com",
        "metadata": {
          "consumer": "projects/PROJECT_NUMBER",
          "service": "datamanager.googleapis.com",
          "containerInfo": "PROJECT_NUMBER",
          "serviceTitle": "Data Manager API",
          "activationUrl": "https://console.cloud.google.com/apis/api/datamanager.googleapis.com/overview?project=PROJECT_NUMBER"
        }
      },
      ...
    ]
  }
}

Help וגם LocalizedMessage

כדאי לבדוק את מטען הנתונים Help ו-LocalizedMessage כדי לקבל קישורים למסמכים ולהודעות שגיאה מותאמות לשפה המקומית, שיעזרו לכם להבין את השגיאה ולתקן אותה.

לדוגמה, הנה Help ו-LocalizedMessage של PERMISSION_DENIEDשגיאה שנגרמת משימוש בפרטי כניסה לפרויקט בענן ב-Google Cloud שבו Data Manager API לא מופעל. Help מטען הייעודי (payload) מציג את כתובת ה-URL שבה אפשר להפעיל את השירות, ובLocalizedMessage יש תיאור של השגיאה.

{
  "error": {
    "code": 403,
    "message": "Data Manager API has not been used in project PROJECT_NUMBER before or it is disabled. Enable it by visiting https://console.cloud.google.com/apis/api/datamanager.googleapis.com/overview?project=PROJECT_NUMBER then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.LocalizedMessage",
        "locale": "en-US",
        "message": "Data Manager API has not been used in project PROJECT_NUMBER before or it is disabled. Enable it by visiting https://console.cloud.google.com/apis/api/datamanager.googleapis.com/overview?project=PROJECT_NUMBER then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry."
      },
      {
        "@type": "type.googleapis.com/google.rpc.Help",
        "links": [
          {
            "description": "Google API Console API activation",
            "url": "https://console.cloud.google.com/apis/api/datamanager.googleapis.com/overview?project=PROJECT_NUMBER"
          }
        ]
      },
      ...
    ]
  }
}

גישה לפרטי השגיאה

אם אתם משתמשים באחת מספריות הלקוח, תוכלו להשתמש בשיטות העזר כדי לקבל את מטען הייעודי (payload) של הפרטים הרגילים.

‎.NET

try {
    // Send API request
}
catch (Grpc.Core.RpcException rpcException)
{
    Console.WriteLine($"Exception encountered: {rpcException.Message}");
    var statusDetails =
        Google.Api.Gax.Grpc.RpcExceptionExtensions.GetAllStatusDetails(
            rpcException
        );
    foreach (var detail in statusDetails)
    {
        if (detail is Google.Rpc.BadRequest)
        {
            Google.Rpc.BadRequest badRequest = (Google.Rpc.BadRequest)detail;
            foreach (
                BadRequest.Types.FieldViolation? fieldViolation in badRequest.FieldViolations
            )
            {
                // Access attributes such as fieldViolation!.Reason and fieldViolation!.Field
            }
        }
        else if (detail is Google.Rpc.RequestInfo)
        {
            Google.Rpc.RequestInfo requestInfo = (Google.Rpc.RequestInfo)detail;
            string requestId = requestInfo.RequestId;
            // Log the requestId...
        }
        else if (detail is Google.Rpc.ErrorInfo)
        {
            Google.Rpc.ErrorInfo errorInfo = (Google.Rpc.ErrorInfo)detail;
            // Log the errorInfo.Reason and errorInfo.Metadata...

            // Log the details in the 'Metadata' map...
            foreach (
                KeyValuePair<String, String> metadataEntry in errorInfo.Metadata
            )
            {
                // Log the metadataEntry.Key and metadataEntry.Value...
            }
        }
        else
        {
            // ...
        }
    }
}

Java

try {
  // Send API request
} catch (com.google.api.gax.rpc.InvalidArgumentException invalidArgumentException) {
  // Gets the standard BadRequest payload from the exception.
  BadRequest badRequest = invalidArgumentException.getErrorDetails().getBadRequest();
  for (int i = 0; i < badRequest.getFieldViolationsCount(); i++) {
    FieldViolation fieldViolation = badRequest.getFieldViolations(i);
    // Access attributes such as fieldViolation.getField() and fieldViolation.getReason()
  }

  // Gets the standard RequestInfo payload from the exception.
  RequestInfo requestInfo = invalidArgumentException.getErrorDetails().getRequestInfo();
  if (requestInfo != null) {
    String requestId = requestInfo.getRequestId();
    // Log the requestId...
  }
} catch (com.google.api.gax.rpc.ApiException apiException) {
  // Fallback exception handler for other types of ApiException.

  // Gets the standard ErrorInfo payload from the exception.
  ErrorInfo errorInfo = apiException.getErrorDetails().getErrorInfo();
  // Log the 'reason' and 'domain'...

  // Log the details in the 'metadata' map...
  for (Entry<String, String> metadataEntry : errorInfo.getMetadataMap().entrySet()) {
    // Log the metadataEntry key and value...
  }

  // Gets the standard RequestInfo payload from the exception.
  RequestInfo requestInfo = invalidArgumentException.getErrorDetails().getRequestInfo();
  if (requestInfo != null) {
    String requestId = requestInfo.getRequestId();
    // Log the requestId...
  }
  ...
}

שיטות מומלצות לטיפול בשגיאות

כדי ליצור אפליקציות עמידות, כדאי להטמיע את השיטות המומלצות הבאות.

בדיקת פרטי השגיאה
תמיד כדאי לחפש אחת ממטען הנתונים של הפרטים הרגילים, כמו BadRequest. כל מטען ייעודי סטנדרטי של פרטים מכיל מידע שיעזור לכם להבין את הסיבה לשגיאה.
הבחנה בין שגיאות בצד הלקוח לבין שגיאות בצד השרת

בודקים אם השגיאה נגרמת בגלל בעיה בהטמעה (הלקוח) או ב-API (השרת).

  • שגיאות בצד הלקוח: קודים כמו INVALID_ARGUMENT, ‏ NOT_FOUND,‏ PERMISSION_DENIED, ‏ FAILED_PRECONDITION, ‏ UNAUTHENTICATED. כדי לפתור את הבעיות האלה, צריך לשנות את הבקשה או את מצב האפליקציה או את פרטי הכניסה שלה. אל תנסו לשלוח את הבקשה שוב בלי לפתור את הבעיה.
  • שגיאות בשרת: קודים כמו UNAVAILABLE, INTERNAL, DEADLINE_EXCEEDED, UNKNOWN. ההודעות האלה מצביעות על בעיה זמנית בשירות ה-API.
הטמעה של אסטרטגיה של ניסיון חוזר

בודקים אם אפשר לנסות שוב לבצע את הפעולה שגרמה לשגיאה, ומשתמשים באסטרטגיה לניסיונות חוזרים.

  • מומלץ לנסות שוב רק במקרה של שגיאות שרת זמניות כמו UNAVAILABLE,‏ DEADLINE_EXCEEDED,‏ INTERNAL,‏ UNKNOWN ו-ABORTED.
  • צריך להשתמש באלגוריתם של השהיה מעריכית לפני ניסיון חוזר כדי להמתין פרקי זמן ארוכים יותר בין הניסיונות החוזרים. כך אפשר להימנע מעומס יתר על שירות שכבר נמצא במצב של עומס. לדוגמה, צריך להמתין שנייה אחת, אחר כך שתי שניות, אחר כך ארבע שניות, ולהמשיך עד למספר המקסימלי של ניסיונות חוזרים או עד לזמן ההמתנה הכולל.
  • מוסיפים כמות קטנה ואקראית של "רעידות" להשהיות של ה-backoff כדי למנוע את בעיית "העדר הרועם", שבה לקוחות רבים מנסים שוב בו-זמנית.
רישום מפורט ביומן

רישום ביומן של תגובת השגיאה המלאה, כולל כל מטען הפרטים הסטנדרטי, במיוחד מזהה הבקשה. המידע הזה חיוני לניפוי באגים ולדיווח על בעיות לתמיכה של Google, אם צריך.

שליחת משוב מהמשתמשים

בהתבסס על הקודים וההודעות במטענים של פרטים רגילים, צריך לספק משוב ברור ומועיל למשתמשים באפליקציה. לדוגמה, במקום "An error occurred" (אירעה שגיאה), אפשר לומר "Transaction ID was missing" (מזהה העסקה היה חסר) או "The account ID of the destination was not found" (מזהה החשבון של היעד לא נמצא).

אם תפעלו לפי ההנחיות האלה, תוכלו לאבחן ולטפל ביעילות בשגיאות שמוחזרות על ידי Data Manager API, וכך ליצור אפליקציות יציבות וידידותיות יותר למשתמשים.