API to retrieve diagnostics reports, OOM crash reports and application logs

This guide shows how developers can retrieve diagnostics reports, out of memory (OOM) crash reports (iOS only), and application logs via API requests.

An example use case is tracking how often app users run into OOM errors.

Runtime/container requirements

  • Runtime version 4.84+
  • For out of memory crash reports, only iOS is currently supported, and container version 21.12.1+ is a requirement

API Endpoints

  1. POST https://flight-recorder.journeyapps.com/api/v1/logs/list - Lists an app’s console logs when a user presses “Upload Report”
  2. POST https://flight-recorder.journeyapps.com/api/v1/reports/list-diagnostics-reports - These are the vanilla diagnostics reports that are generated when a user presses “Upload Report”. Note: these do not contain the application console logs - they are now separately retrieved via the above listed endpoint
  3. POST https://flight-recorder.journeyapps.com/api/v1/reports/list-crash-reports - These contain the same information as diagnostics reports, and are automatically generated when an iOS device runs into an OOM error. They differ from the vanilla diagnostics reports in that they contain the timestamp of the crash, e.g.
    “crashDetails”: {
    “crashType”: “webview”,
    “crashTimestamp”: “2022-01-11T07:49:14.000Z”
    },

Authorization
Authorization is a Bearer Token. Please contact JourneyApps support to retrieve this token for your organization.

Screen Shot 2022-01-12 at 11.27.45 AM

The Body of the request needs to contain the deployment_id, example:

Screen Shot 2022-01-12 at 11.28.35 AM

In OXIDE this deployment_id can be copied from a deployment’s card:

The response will be as follows (this example uses the logs/list endpoint):

Screen Shot 2022-01-12 at 11.30.00 AM

Example implementation

This CloudCode task generates a report that is emailed to stakeholders and contains all crash reports for a time period.

/**
 * To test: Update `emailRecipient` below and run Test Task
 */
const sgMail = require("@sendgrid/mail");
const emailRecipient = "user@example.com"; // Update as required
// To send email, a SendGrid API key is required
sgMail.setApiKey(process.env.sendgrid_api_key);
// The endpoint of the API. Replace with integration endpoint:
const URL_BASE = 'https://flight-recorder.journeyapps.com/api/v1';
/*
    HTTP headers to include in every request.
    Can include Authorization headers here, e.g.
    "Authorization": "Bearer 123456"
*/
const DEFAULT_HEADERS = {
    "Content-Type": "application/json",
    "Authorization": "Bearer " + process.env.flight_recorder
};
export async function run() {
    
    /*
    POST request:
    In this example, the Content-Type is JSON, so we need to use JSON.stringify for the body:
    */
    let res = await postRequest('/reports/list-crash-reports', {body: JSON.stringify({deployment_id: process.env.deployment_id})});
    let response = await res.json();
    console.log(`API Response: ${JSON.stringify(response)}`);
    let items = response.data.items;
    console.log(`items.length ${items.length}`);
    if (response.data.total > response.data.count) {
        // TODO: get remaining data
    }
    // Generate a CSV file
    const csv_entries = await generateCSVRows(items);
    const csv = toCSVArray(csv_entries);
    let file = toBase64(csv);
    // Send the CSV via email
    await sendEmail(file);
}
function generateCSVRows(items) {
    let csv_lines = [['Created At', 'Device ID', 'Device name', 'Crash timestamp', 'Deployment ID', 'Databrowser ID', 'Databrowser label']];
    const csv_entries = items.map(item => [
        item.created_at,
        item.userInfo.app_user_id,
        item.userInfo.user_name,
        item.crashDetails.crashTimestamp,
        item.deployment_id,
        item.backend_id,
        item.userInfo.account_label
    ]);
    csv_lines = csv_lines.concat(csv_entries);
    return csv_lines;
}
function postRequest(path, options) {
    return fetchRequest(path, options, 'POST');
}
function fetchRequest(path, options, method) {
    let opts = options || {};
    let headers = DEFAULT_HEADERS;
    Object.assign(headers, opts.headers);
    Object.assign(opts, {headers: headers, method: method});
    return fetch(URL_BASE + path, opts);
}
function getBasicAuth(username, password) {
    return "Basic " + Buffer.from(username + ":" + password, 'binary').toString('base64');
}
function toCSVArray(array) {
    let csv = array
    .map(function(a) {
        return a.join(",");
    })
    .join("\n");
    console.log(csv);
    return csv;
}
function toBase64(data) {
    return Buffer.from(data).toString("base64");
}
async function sendEmail(generatedCsv) {
    const email = {
        from: "noreply@journeyapps.com",
        to: emailRecipient,
        subject: "Report: Out of memory crashes",
        text: "Find the generated report attached.",
        attachments: [
             {
                content: generatedCsv,
                filename: "attachment.csv"
            }
        ]
    };
    try {
        await sgMail.send(email);
    } catch(error) {
        // This helps for debugging
        if (error.response) {
            console.error(error.response.body);
        }
        throw error;
    }
}

The deployment_id for the request body is currently not available programatically. So in this example it is hardcoded as an environment variable.

Other notes:

  • Apps with runtime version of 4.84 or greater, the “new” diagnostics reports in this API are also accessible via the backend data browser (and now also OXIDE).
  • For apps with runtime version lower than 4.84, the “old” diagnostics reports are still available via the backend data browser.
1 Like