특정 feature가 추가되면서 사용성이 얼마나 되는지를 알기 위해 버튼 클릭 이벤트를 로깅 해야 했습니다.
하지만 클릭할 때마다 서버로 요청을 보내는 방식은 서버 부하가 크고, 네트워크 문제로 실패할 위험도 있었습니다.
그래서 클라이언트에 로그를 모아두었다가, 일정량 이상 쌓이면 서버로 전송하는 방식을 고민하게 되었습니다.
여러 가지 방법을 검토했습니다.
결론적으로, IndexedDB를 선택했습니다.
비동기 처리, 데이터 유지성, 넉넉한 저장 용량이라는 점이 결정적인 이유였습니다.
또한, 로그를 남기는 목적은 개발자가 디버깅하는 용도가 아니라,
사용자의 행동을 분석하고 제품 개선에 활용하려는 목적이었습니다.
var DB_NAME = 'AILoggingDB';
var STORE_NAME = 'logs';
var DB_VERSION = 1;
var AILogger = function() {
var self = this;
self.db = null;
self.initDB = function() {
var request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = function(event) {
console.error('Database error:', event.target.error);
};
request.onupgradeneeded = function(event) {
var db = event.target.result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME, { keyPath: 'timestamp' });
}
};
request.onsuccess = function(event) {
self.db = event.target.result;
};
};
self.initDB();
};
var BATCH_SIZE = 100;
AILogger.prototype.log = function(data) {
var self = this;
return new Promise(function(resolve) {
if (!self.db) {
resolve();
return;
}
var transaction = self.db.transaction([STORE_NAME], 'readwrite');
var store = transaction.objectStore(STORE_NAME);
var logEntry = {
timestamp: new Date().getTime(),
data: data
};
var addRequest = store.add(logEntry);
addRequest.onsuccess = function() {
self.getLogCount().then(function(count) {
if (count >= BATCH_SIZE) {
self.sendLogsToServer().then(resolve);
} else {
resolve();
}
});
};
addRequest.onerror = function() {
console.error('Error logging data');
resolve();
};
});
};
AILogger.prototype.getLogCount = function() {
var self = this;
return new Promise(function(resolve) {
var transaction = self.db.transaction([STORE_NAME], 'readonly');
var store = transaction.objectStore(STORE_NAME);
var countRequest = store.count();
countRequest.onsuccess = function() {
resolve(countRequest.result);
};
});
};
AILogger.prototype.sendLogsToServer = function() {
var self = this;
return new Promise(function(resolve) {
var transaction = self.db.transaction([STORE_NAME], 'readonly');
var store = transaction.objectStore(STORE_NAME);
var getAllRequest = store.getAll();
getAllRequest.onsuccess = function() {
var logs = getAllRequest.result;
if (logs.length === 0) {
resolve();
return;
}
// 서버로 전송 (예: axios 사용)
axios.post('/api/ailogs', logs).then(function() {
// 전송 성공 후 로그 삭제
self.clearLogs().then(resolve);
}).catch(function() {
console.error('Error sending logs');
resolve();
});
};
});
};
AILogger.prototype.clearLogs = function() {
var self = this;
return new Promise(function(resolve) {
var transaction = self.db.transaction([STORE_NAME], 'readwrite');
var store = transaction.objectStore(STORE_NAME);
var clearRequest = store.clear();
clearRequest.onsuccess = function() {
resolve();
};
});
};
로그를 개발자가 직접 보는 것이 아니라,
사용자의 행동 흐름을 간단히 파악하는 목적이라면 이 정도 구조가 충분히 깔끔하고 가벼웠습니다.
IndexedDB를 사용해서 브라우저 안에 데이터를 쌓고,
부하 없이 서버로 천천히 모아서 보내는 방식은 꽤 만족스러웠습니다.
다음에는 쌓인 로그를 조금 더 쉽게 분석할 수 있는 툴을 붙이거나,
자동으로 CSV로 변환하는 기능도 추가해볼 생각입니다.