Skip to content

Commit

Permalink
Add logging for unsuccessful AICamera flows (#2537)
Browse files Browse the repository at this point in the history
* Add sentinel file creation, logging, and deletion logic to AICamera

* Log to server and delete sentinel files after logged; fix location fetch logging

* Use logger.error, not logger.debug, to log to grafana
  • Loading branch information
albullington authored Dec 18, 2024
1 parent f897361 commit 5a2de65
Show file tree
Hide file tree
Showing 13 changed files with 232 additions and 45 deletions.
2 changes: 2 additions & 0 deletions src/appConstants/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ export const photoUploadPath = `${RNFS.DocumentDirectoryPath}/photoUploads`;

export const rotatedOriginalPhotosPath = `${RNFS.DocumentDirectoryPath}/rotatedOriginalPhotos`;

export const sentinelFilePath = `${RNFS.DocumentDirectoryPath}/sentinelFiles`;

export const soundUploadPath = `${RNFS.DocumentDirectoryPath}/soundUploads`;
2 changes: 2 additions & 0 deletions src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Realm from "realm";
import clearCaches from "sharedHelpers/clearCaches.ts";
import { log } from "sharedHelpers/logger";
import { addARCameraFiles } from "sharedHelpers/mlModel.ts";
import { findAndLogSentinelFiles } from "sharedHelpers/sentinelFiles.ts";
import {
useCurrentUser,
useIconicTaxa,
Expand Down Expand Up @@ -95,6 +96,7 @@ const App = ( { children }: Props ): Node => {

useEffect( ( ) => {
addARCameraFiles( );
findAndLogSentinelFiles( );
}, [] );

useEffect( ( ) => {
Expand Down
22 changes: 20 additions & 2 deletions src/components/Camera/AICamera/AICamera.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow

import { useNavigation } from "@react-navigation/native";
import classnames from "classnames";
import FadeInOutView from "components/Camera/FadeInOutView";
import useRotation from "components/Camera/hooks/useRotation.ts";
Expand All @@ -14,11 +15,12 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
import { VolumeManager } from "react-native-volume-manager";
import { convertOfflineScoreToConfidence } from "sharedHelpers/convertScores.ts";
import { log } from "sharedHelpers/logger";
import { deleteSentinelFile, logStage } from "sharedHelpers/sentinelFiles.ts";
import {
useDebugMode, usePerformance, useTranslation
} from "sharedHooks";
import { isDebugMode } from "sharedHooks/useDebugMode";
// import type { UserLocation } from "sharedHooks/useWatchPosition";
import useStore from "stores/useStore";
import colors from "styles/tailwindColors";

import {
Expand Down Expand Up @@ -69,6 +71,9 @@ const AICamera = ( {
setAiSuggestion,
userLocation
}: Props ): Node => {
const navigation = useNavigation( );
const sentinelFileName = useStore( state => state.sentinelFileName );

const hasFlash = device?.hasFlash;
const { isDebug } = useDebugMode( );
const {
Expand Down Expand Up @@ -127,6 +132,7 @@ const AICamera = ( {
};

const handleTakePhoto = useCallback( async ( ) => {
await logStage( sentinelFileName, "take_photo_start" );
setHasTakenPhoto( true );
setAiSuggestion( showPrediction && result );
await takePhotoAndStoreUri( {
Expand All @@ -135,7 +141,13 @@ const AICamera = ( {
navigateImmediately: true
} );
setHasTakenPhoto( false );
}, [setAiSuggestion, takePhotoAndStoreUri, result, showPrediction] );
}, [
setAiSuggestion,
sentinelFileName,
takePhotoAndStoreUri,
result,
showPrediction
] );

useEffect( () => {
if ( initialVolume === null ) {
Expand Down Expand Up @@ -165,6 +177,11 @@ const AICamera = ( {
};
}, [handleTakePhoto, hasTakenPhoto, initialVolume] );

const handleClose = async ( ) => {
await deleteSentinelFile( sentinelFileName );
navigation.goBack( );
};

return (
<>
{device && (
Expand Down Expand Up @@ -255,6 +272,7 @@ const AICamera = ( {
flipCamera={onFlipCamera}
fps={fps}
hasFlash={hasFlash}
handleClose={handleClose}
modelLoaded={modelLoaded}
numStoredResults={numStoredResults}
rotatableAnimatedStyle={rotatableAnimatedStyle}
Expand Down
4 changes: 3 additions & 1 deletion src/components/Camera/AICamera/AICameraButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface Props {
cropRatio?: string;
flipCamera: ( _event: GestureResponderEvent ) => void;
fps?: number;
handleClose: ( ) => void;
hasFlash: boolean;
modelLoaded: boolean;
numStoredResults?: number;
Expand All @@ -48,6 +49,7 @@ const AICameraButtons = ( {
cropRatio,
flipCamera,
fps,
handleClose,
hasFlash,
modelLoaded,
numStoredResults,
Expand Down Expand Up @@ -89,7 +91,7 @@ const AICameraButtons = ( {
className="absolute left-0 bottom-[17px] h-full justify-end flex gap-y-9"
pointerEvents="box-none"
>
<View><Close /></View>
<View><Close handleClose={handleClose} /></View>
</View>
<View
className="absolute right-0 bottom-[6px] h-full justify-end items-end flex gap-y-9"
Expand Down
28 changes: 21 additions & 7 deletions src/components/Camera/AICamera/FrameProcessorCamera.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import {
modelVersion,
taxonomyPath
} from "sharedHelpers/mlModel.ts";
import { logStage } from "sharedHelpers/sentinelFiles.ts";
import {
orientationPatchFrameProcessor,
usePatchedRunAsync
} from "sharedHelpers/visionCameraPatches";
import { useDeviceOrientation } from "sharedHooks";
// import type { UserLocation } from "sharedHooks/useWatchPosition";
import useStore from "stores/useStore";

type Props = {
// $FlowIgnore
Expand Down Expand Up @@ -77,6 +78,7 @@ const FrameProcessorCamera = ( {
resetCameraOnFocus,
userLocation
}: Props ): Node => {
const sentinelFileName = useStore( state => state.sentinelFileName );
const { deviceOrientation } = useDeviceOrientation();
const [lastTimestamp, setLastTimestamp] = useState( undefined );

Expand Down Expand Up @@ -113,7 +115,7 @@ const FrameProcessorCamera = ( {
} );

return unsubscribeBlur;
}, [navigation, resetCameraOnFocus] );
}, [navigation, resetCameraOnFocus, sentinelFileName] );

const handleResults = Worklets.createRunOnJS( ( result, timeTaken ) => {
setLastTimestamp( result.timestamp );
Expand Down Expand Up @@ -148,7 +150,7 @@ const FrameProcessorCamera = ( {
}
}

patchedRunAsync( frame, () => {
patchedRunAsync( frame, ( ) => {
"worklet";

// Reminder: this is a worklet, running on a C++ thread. Make sure to check the
Expand Down Expand Up @@ -202,10 +204,22 @@ const FrameProcessorCamera = ( {
cameraRef={cameraRef}
device={device}
frameProcessor={frameProcessor}
onCameraError={onCameraError}
onCaptureError={onCaptureError}
onClassifierError={onClassifierError}
onDeviceNotSupported={onDeviceNotSupported}
onCameraError={async ( ) => {
await logStage( sentinelFileName, "fallback_camera_error" );
onCameraError( );
}}
onCaptureError={async ( ) => {
await logStage( sentinelFileName, "camera_capture_error" );
onCaptureError( );
}}
onClassifierError={async ( ) => {
await logStage( sentinelFileName, "camera_classifier_error" );
onClassifierError( );
}}
onDeviceNotSupported={async ( ) => {
await logStage( sentinelFileName, "camera_device_not_supported_error" );
onDeviceNotSupported( );
}}
pinchToZoom={pinchToZoom}
inactive={inactive}
/>
Expand Down
10 changes: 6 additions & 4 deletions src/components/Camera/Buttons/Close.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { useNavigation } from "@react-navigation/native";
import { TransparentCircleButton } from "components/SharedComponents";
import React from "react";
import { useTranslation } from "sharedHooks";

const Close = ( ) => {
interface Props {
handleClose: ( ) => void;
}

const Close = ( { handleClose }: Props ) => {
const { t } = useTranslation( );
const navigation = useNavigation( );

return (
<TransparentCircleButton
onPress={( ) => navigation.goBack( )}
onPress={handleClose}
accessibilityLabel={t( "Close" )}
accessibilityHint={t( "Navigates-to-previous-screen" )}
icon="close"
Expand Down
54 changes: 49 additions & 5 deletions src/components/Camera/CameraContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
} from "components/Camera/helpers/visionCameraWrapper";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState
Expand All @@ -12,6 +13,7 @@ import { Alert, StatusBar } from "react-native";
import type {
TakePhotoOptions
} from "react-native-vision-camera";
import { createSentinelFile, deleteSentinelFile, logStage } from "sharedHelpers/sentinelFiles.ts";
import { useDeviceOrientation, useTranslation, useWatchPosition } from "sharedHooks";
import useLocationPermission from "sharedHooks/useLocationPermission.tsx";
import useStore from "stores/useStore";
Expand All @@ -28,6 +30,24 @@ const CameraContainer = ( ) => {
const setCameraState = useStore( state => state.setCameraState );
const evidenceToAdd = useStore( state => state.evidenceToAdd );
const cameraUris = useStore( state => state.cameraUris );
const sentinelFileName = useStore( state => state.sentinelFileName );
const setSentinelFileName = useStore( state => state.setSentinelFileName );

const { params } = useRoute( );
const cameraType = params?.camera;

const logStageIfAICamera = useCallback( async (
stageName: string,
stageData: string
) => {
if ( cameraType !== "AI" ) { return; }
await logStage( sentinelFileName, stageName, stageData );
}, [cameraType, sentinelFileName] );

const deleteStageIfAICamera = useCallback( async ( ) => {
if ( cameraType !== "AI" ) { return; }
await deleteSentinelFile( sentinelFileName );
}, [cameraType, sentinelFileName] );

const { deviceOrientation } = useDeviceOrientation( );
// Check if location permission granted b/c usePrepareStoreAndNavigate and
Expand All @@ -44,8 +64,6 @@ const CameraContainer = ( ) => {
} );
const navigation = useNavigation( );
const { t } = useTranslation( );
const { params } = useRoute( );
const cameraType = params?.camera;

const [cameraPosition, setCameraPosition] = useState<"front" | "back">( "back" );
// https://react-native-vision-camera.com/docs/guides/devices#selecting-multi-cams
Expand All @@ -71,6 +89,23 @@ const CameraContainer = ( ) => {

const camera = useRef<Camera>( null );

useEffect( () => {
const generateSentinelFile = async ( ) => {
const fileName = await createSentinelFile( "AICamera" );
setSentinelFileName( fileName );
};
if ( cameraType !== "AI" ) { return; }
generateSentinelFile( );
}, [setSentinelFileName, cameraType] );

const logFetchingLocation = !!( hasPermissions && sentinelFileName );

useEffect( ( ) => {
if ( logFetchingLocation ) {
logStageIfAICamera( "fetch_user_location_start" );
}
}, [logStageIfAICamera, logFetchingLocation] );

const {
hasPermissions: hasSavePhotoPermission,
hasBlockedPermissions: hasBlockedSavePhotoPermission,
Expand Down Expand Up @@ -105,23 +140,29 @@ const CameraContainer = ( ) => {
const handleNavigation = useCallback( async ( newPhotoState = {} ) => {
await prepareStoreAndNavigate( {
...navigationOptions,
newPhotoState
newPhotoState,
logStageIfAICamera,
deleteStageIfAICamera
} );
}, [
prepareStoreAndNavigate,
navigationOptions
navigationOptions,
logStageIfAICamera,
deleteStageIfAICamera
] );

const handleCheckmarkPress = useCallback( async newPhotoState => {
if ( !showPhotoPermissionsGate ) {
await handleNavigation( newPhotoState );
} else {
await logStageIfAICamera( "request_save_photo_permission_start" );
requestSavePhotoPermission( );
}
}, [
handleNavigation,
requestSavePhotoPermission,
showPhotoPermissionsGate
showPhotoPermissionsGate,
logStageIfAICamera
] );

const toggleFlash = ( ) => {
Expand Down Expand Up @@ -167,7 +208,9 @@ const CameraContainer = ( ) => {
// this does leave a short period of time where the camera preview is still active
// after taking the photo which we might to revisit if it doesn't look good.
const cameraPhoto = await camera?.current?.takePhoto( takePhotoOptions );
await logStageIfAICamera( "take_photo_complete" );
if ( !cameraPhoto ) {
await logStageIfAICamera( "take_photo_error" );
throw new Error( "Failed to take photo: missing camera" );
}
if ( options?.inactivateCallback ) options.inactivateCallback();
Expand Down Expand Up @@ -214,6 +257,7 @@ const CameraContainer = ( ) => {
onRequestGranted: ( ) => console.log( "granted in save photo permission gate" ),
onRequestBlocked: ( ) => console.log( "blocked in save photo permission gate" ),
onModalHide: async ( ) => {
await logStageIfAICamera( "request_save_photo_permission_complete" );
await handleNavigation( {
cameraUris,
evidenceToAdd
Expand Down
Loading

0 comments on commit 5a2de65

Please sign in to comment.