앱을 플레이 스토어에 심사를 걸어두고 마저 작업을 이어서 하려고 했다. 그러다가 문득 생각이 든 것이 현재 프론트엔드에서는 firebase console에서 프로젝트가 onestep-dev 하나밖에 없어서 개발 중에도 모든 이벤트 로그가 다 찍히고 있었다. 그래서 이번 기회에 프론트엔드에서 사용하는 툴들을 환경에 맞게 구분해보려고 한다.
목표
1. 환경을 development, staging, production으로 구분한다.
2. 툴들에서도 환경에 맞게 프로젝트를 구분한다.
3. apk 혹은 aab로 빌드했을 때 환경 변수가 잘 설정되었는지 확인할 수 있다.
이전에 환경 변수를 설정할 때 대략적으로 설정해두었다.
app.config.js에서 APP_MODE라는 환경 변수로 development 혹은 production를 구분했고 그것에 따라 sentry environment와 api 서버 주소를 구분했다.
APP_MODE는 터미널 커멘드에서 직접 주입하거나 package.json에 커멘드를 등록해두고 사용했었다.
그리고 빌드할 땐 eas.json에서 profile에 따라 expo 환경변수를 설정해두었다. 환경변수 관련 예전 글🔗
이제 staging 환경도 추가해볼 것
// eas.json
{
"cli": {
"version": ">= 10.0.2"
},
"build": {
"staging": {
"distribution": "internal",
"channel": "staging",
"android": {
"applicationArchivePath": "android/app/build/outputs/apk/release/app-release.apk",
"buildType": "apk"
},
"env": {
"EXPO_PUBLIC_APP_MODE": "staging"
}
},
"production": {
"channel": "production",
"node": "20.15.0",
"env": {
"EXPO_PUBLIC_APP_MODE": "production"
}
}
},
"submit": {
"production": {}
}
}
eas build를 할 때엔 기능 다 완성하고 실기기에서 잘 동작하는지 확인할 때라 development환경이 필요하지 않아서 뺐다.
staging일 땐 빠르게 설치하고 확인해보기 위해 apk로 빌드하기로 했다.
// app.config.js
let Config = {
BASE_URL: '',
SENTRY_MODE: '',
};
switch (process.env.APP_MODE || process.env.EXPO_PUBLIC_APP_MODE) {
case 'production':
Config.BASE_URL = '우리 서버 url';
Config.SENTRY_MODE = 'production';
break;
case 'staging':
Config.BASE_URL = 'https://dev.우리 서버 url';
Config.SENTRY_MODE = 'staging';
break;
case 'development':
Config.BASE_URL = 'https://dev.우리 서버 url';
Config.SENTRY_MODE = 'development';
break;
case 'local':
Config.BASE_URL = 'http://10.0.2.2:8000';
Config.SENTRY_MODE = 'development';
break;
default:
throw new Error('Invalid APP_MODE');
}
//...
그리고 app.config.js에서 환경에 따라 api 서버 url과 sentry 환경을 설정하도록 했다.
그리고 가끔 dev 서버가 죽는 경우도 있어서 로컬 서버 케이스도 만들었다.
development 환경일 때 :
Sentry를 꺼두고 싶다. 개발할 땐 에러가 잘 잡히기 때문에 Sentry로 수집해야할 이유가 없다고 생각한다.
staging 환경일 때 :
발생하는 모든 에러를 수집했으면 좋겠다.
production 환경일 때 :
발생하는 모든 에러를 수집하기는 너무 많은 것 같아 samplerate를 조금 줄여보려고 한다. 하지만 아직 사용자가 매우 적기 때문에 1.0을 유지하되 문제를 파악하기 힘들 정도로 에러가 쌓인다면 그때 대응해도 좋을 것 같다.
//constants/env.js
//...
export const env = {
BASE_URL: getEnv('BASE_URL'),
SENTRY_MODE: getEnv('SENTRY_MODE'),
};
export const getSentryConfig = () => {
switch (env.SENTRY_MODE) {
case 'production':
return {
tracesSampleRate: 1.0,
_experiments: {
profilesSampleRate: 1.0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 1.0,
},
};
case 'staging':
return {
tracesSampleRate: 1.0,
_experiments: {
profilesSampleRate: 1.0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 1.0,
},
};
}
};
//...
//app/_layout.jsx
//...
import { env, getSentryConfig } from '@/constants/env';
const SENTRY_MODE = env.SENTRY_MODE;
Sentry.init({
enabled: ['staging', 'production'].includes(SENTRY_MODE)
environment: SENTRY_MODE,
dsn: 'dsn 주소',
...getSentryConfig(),
integrations: [
Sentry.mobileReplayIntegration({
maskAllText: true,
maskAllImages: true,
}),
],
});
//...
이런 식으로 SENTRY_MODE가 staging이나 production일 때만 sentry를 enable하게 하고 설정값은 env.js에서 getSentryConfig라는 함수로 받아오게 만들었다.
staing이나 production일때만 sentry가 enable되니까 getSentryConfig에서 다른 케이스는 고려하지 않았다.
그리고 env.js 파일 안에서 바로 SENTRY_MODE 값을 읽어와서 혹시나 오타가 난다거나 하는 경우를 막아보려고 해보았다!
//app.config.js
//...
let googleServiceJson = null; //default
switch (process.env.APP_MODE || process.env.EXPO_PUBLIC_APP_MODE) {
case 'production':
Config.BASE_URL = '우리 서버 url';
Config.SENTRY_MODE = 'production';
googleServiceJson =
process.env.GOOGLE_SERVICES_PRODUCTION_JSON ||
'./google-services.production.json';
break;
case 'staging':
Config.BASE_URL = '우리 서버 url';
Config.SENTRY_MODE = 'staging';
googleServiceJson =
process.env.GOOGLE_SERVICES_STAGING_JSON ||
'./google-services.staging.json';
break;
case 'development':
Config.BASE_URL = '우리 서버 url';
Config.SENTRY_MODE = 'development';
break;
case 'local':
Config.BASE_URL = 'http://10.0.2.2:8000';
Config.SENTRY_MODE = 'development';
break;
default:
throw new Error('Invalid APP_MODE');
}
//...
우선 firebase에서 staging, production, development환경에 맞게 프로젝트를 새로 파고 그거에 맞는 google services json을 eas secrets에 등록해두었다. 그래서 eas build할 때 eas.json에 설정된 환경 변수에 따라서 secrets에서 불러올 수 있게 했다. 그리고 로컬에서 확인할 때를 위해 프로젝트 루트 경로에도 json 파일을 넣어두고 gitignore에 설정해두었다.
그리고 development일 때는 firebase로 사용자 로그를 수집할 이유가 없어서
let googleServiceJson = null로 해두고
//app.config.js
//...
const expoConfig = {
expo: {
//...
android: {
adaptiveIcon: {
foregroundImage: './assets/adaptive-icon.png',
backgroundColor: '#ffffff',
},
package: '패키지 이름',
versionCode: 1,
},
//...
}
if (googleServiceJson) {
expoConfig.expo.android.googleServicesFile = googleServiceJson;
}
module.exports = expoConfig;
이렇게 production이나 staging 환경이라서 googleServiceJson 값이 생겼을 때만 expoConfig.expo.android에 값을 넣어주고 exports하는 식으로 해보았다.
에뮬레이터로 실행할 때 확인할 방법은 있는데 실기기에서도 잘 연결되었는가 확인할 방법이 찾아보아도 잘 나오지 않는다. 다들 어떻게 하는 걸까
일단 에뮬레이터에선...
sentry
sdk가 initialize된 직후 실행할 콜백 함수를 전달하는 onReady옵션이 있었는데 나중에 적용해봐야겠다. 지금은 원래 목록에 없던 staging 환경과 production 환경이 sentry 목록에 생겨서 잘 연결된 걸 확인했기 때문.
나중에 문제가 생기면 그때 다시 해볼 것.
firebase analytics
firebase console에는 로그 값 반영이 몇시간 후였나 매우 늦게 반영되기 때문에 디버그 뷰로 확인하면 될 것 같다. 디버그뷰🔗
이때 안드로이드의 경우
adb shell setprop debug.firebase.analytics.app PACKAGE_NAME
이 명령어에서 PACKAGE_NAME은 com.으로 시작하는 그 패키지 이름이다.
expo에서 CNG(Continuous Native Generation)에 대해 조금 더 알게되었다. 지금까지 문서에서 몇번 보았지만 뭘 하는 건지 몰랐는데 이번에 app.config.js를 고쳐보면서 알게된 것은