function pushMetric(action, log, metricObj) {
    const {
        bucket,
        keys,
        versionId,
        byteLength,
        newByteLength,
        oldByteLength,
        numberOfObjects,
        authInfo,
        canonicalID,
        location,
        isDelete,
        removedDeleteMarkers,
    } = metricObj;

    if (utapiVersion === 2) {
        const incomingBytes = action === 'getObject' ? 0 : newByteLength;
        let sizeDelta = incomingBytes;
        if (Number.isInteger(oldByteLength) && Number.isInteger(newByteLength)) {
            sizeDelta = newByteLength - oldByteLength;
            // Include oldByteLength in conditional so we don't end up with `-0`
        } else if (action === 'completeMultipartUpload' && !versionId && oldByteLength) {
            // If this is a non-versioned bucket we need to decrement
            // the sizeDelta added by uploadPart when completeMPU is called.
            sizeDelta = -oldByteLength;
        } else if (action === 'abortMultipartUpload' && byteLength) {
            sizeDelta = -byteLength;
        } else if (action === 'putDeleteMarkerObject' && byteLength) {
            sizeDelta = -byteLength;
        }

        let objectDelta = isDelete ? -numberOfObjects : numberOfObjects;
        // putDeleteMarkerObject does not pass numberOfObjects
        if ((action === 'putDeleteMarkerObject' && byteLength === null)
            || action === 'replicateDelete'
            || action === 'replicateObject') {
            objectDelta = 1;
        } else if (action === 'multiObjectDelete') {
            objectDelta = -(numberOfObjects + removedDeleteMarkers);
        }

        const utapiObj = {
            operationId: action,
            bucket,
            location,
            objectDelta,
            sizeDelta: isDelete ? -byteLength : sizeDelta,
            incomingBytes,
            outgoingBytes: action === 'getObject' ? newByteLength : 0,
        };

        // Any operation from lifecycle that does not change object count or size is dropped
        const isLifecycle = _config.lifecycleRoleName
            && authInfo && authInfo.arn.endsWith(`:assumed-role/${_config.lifecycleRoleName}/backbeat-lifecycle`);
        if (isLifecycle && !objectDelta && !sizeDelta) {
            log.trace('ignoring pushMetric from lifecycle service user', { action, bucket, keys });
            return undefined;
        }

        if (keys && keys.length === 1) {
            [utapiObj.object] = keys;
            if (versionId) {
                utapiObj.versionId = versionId;
            }
        }

        utapiObj.account = authInfo ? evalAuthInfo(authInfo, canonicalID, action).accountId : canonicalID;
        utapiObj.user = authInfo ? evalAuthInfo(authInfo, canonicalID, action).userId : undefined;
        return utapi.pushMetric(utapiObj);
    }
    const utapiObj = {
        bucket,
        keys,
        byteLength,
        newByteLength,
        oldByteLength,
        numberOfObjects,
    };
    // If `authInfo` is included by the API, get the account's canonical ID for
    // account-level metrics and the shortId for user-level metrics. Otherwise
    // check if the canonical ID is already provided for account-level metrics.
    if (authInfo) {
        const { accountId, userId } = evalAuthInfo(authInfo, canonicalID, action);
        utapiObj.accountId = accountId;
        utapiObj.userId = userId;
    } else if (canonicalID) {
        utapiObj.accountId = canonicalID;
    }
    return utapi.pushMetric(action, log.getSerializedUids(), utapiObj);
}
Call the Utapi Client `pushMetric` method with the associated parameters
@param {string} action - the metric action to push a metric for
@param {object} log - werelogs logger
@param {object} metricObj - the object containing the relevant data for
pushing metrics in Utapi
@param {string} [metricObj.bucket] - (optional) bucket name
@param {AuthInfo} [metricObj.authInfo] - (optional) Instance of AuthInfo
class with requester's info
@param {number} [metricObj.canonicalID] - (optional) The account's canonical
ID used for the request
@param {number} [metricObj.byteLength] - (optional) current object size
(used, for example, for pushing 'deleteObject' metrics)
@param {number} [metricObj.newByteLength] - (optional) new object size
@param {number|null} [metricObj.oldByteLength] - (optional) old object size
(obj. overwrites)
@param {number} [metricObj.numberOfObjects] - (optional) number of objects
added/deleted
@param {boolean} [metricObject.isDelete] - (optional) Indicates whether this
is a delete operation
@return {function | undefined} - `utapi.pushMetric` or undefined if the action is
filtered out and not pushed to utapi.