프로그램을 개발하다보면, 설치, 삭제, 업데이트를 얼마나 했는지, 어떤 기능을 얼마나 사용했는지 통계 정보가 필요할 때가 있다. 사용자가 특정 기능을 많이 사용한다면 그 기능을 더 개선하는 방향으로 업데이트 계획을 세울 수도 있다. 사용자가 하루에 프로그램을 몇 번 실행하는지, 중요한 오류 상황에서 서버에 로깅을 어떻게 남기는지 알아보겠다.
이때까지 서버의 정보를 가져오는 것은 버전체크기능 뿐이었는데, 이때 사용한 API는 URLDownloadToCacheFile 였다. 이 API로는 서버에 특정 정보를 보내는 기능이 어려운 HttpClient를 구현할 필요가 있다.
HttpClient를 구현한 오픈소스는 너무나 많고, 나도 Wininet 기반으로 구현해 본 것이 있지만 여러가지 이유로 여기서는 http://www.codeproject.com/Articles/66625/A-Fully-Featured-Windows-HTTP-Wrapper-in-C
클릭 정보를 저장할 레지스트리 위치과, 서버에 클릭통계를 저장할 주소와 서버 로깅을 저장하 주소를 지정한다.
사용
# define _CLICK(name) dfx::ClickStat(name).Click()
...
if( _GetInt(L"autoUpdate", 1) )
{
_CheckVersion(false);
_CLICK(L"opt.autoupdate_no");
}
else
{
_CLICK(L"opt.autoupdate_yes");
}
_CLICK(L"app.run");
// 통계전송
dfx::SendAllClickStatAsync();
_CLICK 매크로를 이용해서 레지스트리에 클릭 횟수를 저장한다. SendAllClickStatAsync 는 레지스트리에서 모든 DWORD값을 읽어와서 서버로 POST 데이타로 전송한다. 쓰레드를 이용해서 비동기로 전송한다.
서버는
mysql table
create table clickstat (
_id integer not null auto_increment primary key,
proj char(10),
date1 date,
name char(30),
count int
);
alter table clickstat add unique dailyUniqueNameKey (proj, date1, name);
--logging
create table logging (
_id integer not null auto_increment primary key,
date1 date,
proj char(10),
module char(50),
version char(50),
file char(255),
line integer,
tag char(20),
msg char(255),
text1 text);
clickstat.php
<?php
include "config.php";
$proj = $_GET['proj'];
if( $proj == '' )
{
header('HTTP/1.1 400 Bad Request (proj)');
exit;
}
if( $_SERVER['HTTP_CLICKSTAT'] != 'ItsClickStatistics')
{
header('HTTP/1.1 400 Bad Request (clickstat)');
exit;
}
$today = date("Y-m-d");
$db_conn = mysql_connect($_dbhost, $_dbuser, $_dbpass);
if(mysql_select_db($_dbname, $db_conn) )
{
foreach( $_POST as $name => $count )
{
$sql = "insert into clickstat (proj, date1, name, count) values ('$proj', '$today', '$name', $count) on duplicate key update count = count + $count";
mysql_query($sql, $db_conn);
}
mysql_close($db_conn);
}
?>
table에 필드 3개를 묶어서 Unique로 설정하고 insert into ......on duplicat key update ... 를 이용하여 insert 실패시 업데이트가 이루어 지도록 쿼리를 작성한다. 쿼리를 이렇게 작성하면 시간 타이밍적으로 중복으로 요청이 쌓이는 것을 막을 수 있다.
설치삭제 업데이트 통계
nsis 셋의 스크립트 코드
ReadRegStr $R0 HKLM "${_APPREGKEY}" "InstallDir"
${If} $R0 == ""
ExecWait '"$INSTDIR\${_APP_FILENAME}" /install'
${Else}
ExecWait '"$INSTDIR\${_APP_FILENAME}" /update'
${EndIf}
# 언인스톨..
ExecWait '"$INSTDIR\${_APP_FILENAME}" /uninstall'
설치중에 InstallDir에 값이 있으면 이미 설치된 것으로 판단하여 /update 파리미터를 전송한다.
메인 어플에서는 통계전송을 위한 파라미터가 있으면 통계전송만 하고 프로그램을 바로 종료한다.
BOOL CEasyRegistryApp::InitInstance()
{
dfx::ClickStat::Initialize(HKEY_CURRENT_USER,
L"Software\\EasyRegistry\\clickstat",
L"http://mdiwebma.com/easyregistry/clickstat.php?proj=ER",
L"http://mdiwebma.com/easyregistry/logging.php?proj=ER");
// 설치 삭제 업데이트 통계 전송 //nsis 셋업에서 호출됨..
CCommandLine cmd;
if( cmd.Check(L"install") )
{
dfx::SendClickStat(L"app.install", 1);
return FALSE;
}
else if( cmd.Check(L"uninstall") )
{
dfx::SendClickStat(L"app.uninstall", 1);
return FALSE;
}
else if( cmd.Check(L"update") )
{
dfx::SendClickStat(L"app.update", 1);
return FALSE;
}
SendClickStat 함수는 동기적으로 서버에 클릭 통계를 전송한다.
서버 클릭 통계 확인
http://mdiwebma.com/easyregistry/clickstat_view.php?proj=ER
app_run: 2
app_update: 1
mnu_checkversion: 1
mnu_runregedit: 1
opt_autoupdate_no: 2
서버로깅
dfx::SendLog(L"Unittest", L"1.0", L"unittest.cpp", __LINE__, L"Info", L"Msg", L"Text");
중요한 에러나 예외상황에서 서버에 로기를 남기는 코드가 필요하다.
http://mdiwebma.com/easyregistry/logging_view.php?proj=ER
2013-08-30 [ Unittest ] 1.0 , unittest.cpp (286) Info : Msg
Text
2013-08-30 [ Unittest ] 1.0 , unittest.cpp (286) Info : Msg
Text
중대한 오류가 발생했다고 사용자게 메시지박스를 띄우는 것은 쉬우나 그 메시지 알림을 개발자에 기꺼이 알려주는 사용자는 극히 드물다. 당연히 예외가 발생하지 않아야 하는 코드에 예외 발생시 서버로깅 코드를 추가 해 두면 나중에 프로그램의 버그를 잡고 개선하는데 많은 도움이 된다.