본문 바로가기

IT/Tizen

[Tizen] 타이젠 앱에 디버그 로그 심어보기

개발자로서 살아온 기간내내

함수 안팎으로 로그를 '신나게' 심어왔습니다.


코드에 로그가 없는 경우는,

- 아직 로그를 심기 직전이거나

- 동작을 확인하고 로그를 지운 뒤겠죠.


앱이 물고 있는 프레임워크의 동작이 언제나 완벽하다면,

앱에서 로그를 출력할 일은 크게 줄어들 것입니다.


하지만, 언제나 프레임워크는 개발막바지가 되어야 비로소 쓸만해지죠. :)

모든 함수에 에러체크는 필수이고,

에러로그는 가장 쉬우면서 확실한 디버깅 방법입니다.


안녕하세요, Tizen 개발자 윤진입니다.


타이젠은 플랫폼 차원에서 로그를 남길 수 있는 방법을 제공합니다.

printf(), fprintf()도 사용할 수 있지만,

- SDK에 로그를 노출하거나,

- 시스템 로그들과 함께 사용하기 위해서는,

타이젠에서 제공하는 dlog를 사용해야 합니다.


타이젠 개발자 사이트(http://developer.tizen.org)에 들어가서,

하단의 API References 메뉴에 들어갑니다.



왼편에 있는 프레임에서,

API References > Native Application > Native API Reference > System > dlog

위의 항목을 선택합니다.



플랫폼에서 지원하는 로그를 위한 함수는 2가지가 있습니다.

dlog_print()와 dlog_vprint()입니다.

위의 두 함수는 공통적으로 priority와 tag를 입력해주어야 합니다.


priority는 출력할 로그의 종류에 따라 선택하면 되죠.

개발자의 필요에 따라 사용하는 로그이므로,

priority 사용에 제한을 두진 않습니다.

(소스 출처 : git://review.tizen.org/framework/system/dlog, tizen_2.3)


 * @brief log priority values, in ascending priority order.
 * @since_tizen 2.3
 */
typedef enum {
    DLOG_UNKNOWN = 0, /**< Keep this always at the start */
    DLOG_DEFAULT, /**< Default */
    DLOG_VERBOSE, /**< Verbose */
    DLOG_DEBUG, /**< Debug */
    DLOG_INFO, /**< Info */
    DLOG_WARN, /**< Warning */
    DLOG_ERROR, /**< Error */
    DLOG_FATAL, /**< Fatal */
    DLOG_SILENT, /**< Silent */
    DLOG_PRIO_MAX   /**< Keep this always at the end. */
} log_priority;


tag는 일반적으로 자신의 모듈명을 입력합니다.

플랫폼에서 사용하는 tag는 대부분 대문자와 '_'(underscore)를 사용하고 있네요.


#define LOG_TAG "BUNDLE"

#define LOG_TAG "PKGMGR"

#define LOG_TAG "CAPI_APPFW_APPLICATION"

#define LOG_TAG "VOLUME"


하지만, 소문자만으로 태그이름을 정한 모듈도 있긴 합니다.

이를 보건대 태그이름에 대한 정책적인 제약은 없는 것으로 보이네요.

모듈로그는 태그명으로 검색할 수 있으므로 차후를 위해 잘 기억해두세요.


/**
 * @addtogroup CAPI_SYSTEM_DLOG
 * @{
 */
/**
 * @brief     Send log with priority and tag.
 * @details   for application
 * @since_tizen 2.3
 * @param[in] prio log_priority
 * @param[in] tag tag
 * @param[in] fmt format string
 * @return On success, the function returns the number of bytes written.
 *         On error, a negative errno-style error code
 * @retval #DLOG_ERROR_INVALID_PARAMETER Invalid parameter
 * @retval #DLOG_ERROR_NOT_PERMITTED Operation not permitted
 * @pre       none
 * @post      none
 * @see       dlog_vprint
 * @code
#include<dlog.h>
int main(void)
{
    int integer = 21;
    char string[] = "test dlog";

    dlog_print(DLOG_INFO, "USR_TAG", "test dlog");
    dlog_print(DLOG_INFO, "USR_TAG", "%s, %d", string, integer);
    return 0;
}
 * @endcode
 */
int dlog_print(log_priority prio, const char *tag, const char *fmt, ...);
/**
 * @brief     Send log with priority, tag and va_list.
 * @details   for application
 * @since_tizen 2.3
 * @param[in] prio log_priority
 * @param[in] tag tag
 * @param[in] fmt format string
 * @param[in] ap va_list
 * @return On success, the function returns the number of bytes written.
 *         On error, a negative errno-style error code
 * @retval #DLOG_ERROR_INVALID_PARAMETER Invalid parameter
 * @retval #DLOG_ERROR_NOT_PERMITTED Operation not permitted
 * @pre       none
 * @post      none
 * @see       dlog_print
 * @code
#include<dlog.h>
void my_debug_print(char *format, ...)
{
    va_list ap;

    va_start(ap, format);
    dlog_vprint(DLOG_INFO, "USR_TAG", format, ap);
    va_end(ap);
}

int main(void)
{
    my_debug_print("%s", "test dlog");
    my_debug_print("%s, %d", "test dlog", 21);
    return 0;
}
 * @endcode
 */
int dlog_vprint(log_priority prio, const char *tag, const char *fmt, va_list ap);


함수 정의를 살펴보면,

priority와 tag를 지정한 뒤,

로그로 출력하고자 하는 포맷을 넣어주면 됩니다.

두 함수 모두 내부적으로 동일한 루틴을 따르네요.


static int __write_to_log_sd_journal(log_id_t log_id, log_priority prio, const char *tag, const char *msg)
{
    pid_t tid = (pid_t)syscall(SYS_gettid);
    /* XXX: sd_journal_sendv() with manually filed iov-s might be faster */
    return sd_journal_send("MESSAGE=%s", msg,
                   "PRIORITY=%i", dlog_pri_to_journal_pri(prio),
                   "LOG_TAG=%s", tag,
                   "LOG_ID=%s", dlog_id_to_string(log_id),
                   "TID=%d", tid,
                   NULL);
}


dlog는 내부적으로 systemd의 journal을 사용합니다.

sd-journal(systemd's journal)은 systemd에서 관리하는 로그를 위한 서비스입니다.

dlog는 sd_journal_send()를 사용하여 sd-journal에 로그를 입력하고 있습니다.

sd-journal에 대한 이야기는 기회가 있을때 다시 하기로 하죠.


앱에서는 로그를 수도 없이 사용하므로,

간단히 사용할 수 있는 매크로 함수를 사용하기로 합니다.

실제 타이젠의 시스템 앱에서 사용하고 있는 매크로 함수가 있습니다.

시스템 앱에서 사용하고 있는 매크로 함수를,

SDK로 개발하는 앱에서 사용할 수 있도록 수정하였습니다.

(소스출처 : http://www.freedesktop.org/software/systemd/man/sd-journal.html, master)


#ifndef __YOUR_APPLICATION_LOG_H__
#define __YOUR_APPLICATION_LOG_H__

#include <dlog.h>

#undef LOG_TAG
#define LOG_TAG "YOUR_APPLICATION"

#define COLOR_RED "\033[0;40;31m"
#define COLOR_LIGHTBLUE "\033[1;40;34m"
#define COLOR_YELLOW "\033[1;40;33m"
#define COLOR_END "\033[0;m"
#define COLOR_GRAY "\033[1;40;30m"
#define COLOR_MAGENTA "\033[1;35m"

#if !defined(_D)
#define _D(fmt, arg...) dlog_print(DLOG_DEBUG, LOG_TAG, COLOR_YELLOW fmt COLOR_END"\n", ##arg)
#endif

#if !defined(_W)
#define _W(fmt, arg...) dlog_print(DLOG_WARN, LOG_TAG, COLOR_MAGENTA fmt COLOR_END"\n", ##arg)
#endif

#if !defined(_E)
#define _E(fmt, arg...) dlog_print(DLOG_ERROR, LOG_TAG, COLOR_RED fmt COLOR_END"\n", ##arg)
#endif

#define retv_if(expr, val) do { \
    if(expr) { \
        _E("(%s) -> %s() return", #expr, __FUNCTION__); \
        return (val); \
    } \
} while (0)

#define ret_if(expr) do { \
    if(expr) { \
        _E("(%s) -> %s() return", #expr, __FUNCTION__); \
        return; \
    } \
} while (0)

#define goto_if(expr, val) do { \
    if(expr) { \
        _E("(%s) -> goto", #expr); \
        goto val; \
    } \
} while (0)

#define break_if(expr) { \
    if(expr) { \
        _E("(%s) -> break", #expr); \
        break; \
    } \
}

#define continue_if(expr) { \
    if(expr) { \
        _E("(%s) -> continue", #expr); \
        continue; \
    } \
}

#endif                /* __YOUR_APPLICATION_LOG_H__ */


헤더를 사용하려면 아래 로그헤더를 다운로드 받으시면 됩니다.

log.h

헤더를 사용하시려면,

- LOG_TAG에 자신의 모듈이름을 적어주세요.

- 일반적인 디버그 상황에서 사용하는 디버그 로그는 _D 매크로 함수를 사용하고,

    _D("%s's GEOMETRY : [%d, %d, %d, %d]", (const char *) data, x, y, w, h);

- 에러는 아니지만, 일반적인 디버그 로그보다 위험한 경우는 워닝 로그 _W 매크로 함수를 사용하고,

    _W("cannot find index.");

- 에러 상황에서는 _E 매크로 함수를 사용하시면 됩니다.

    _E("Failed to remove vconf %s", "memory/menuscreen/mapbuf");


로그를 위한 장치가 준비되었으니,

이제 본격적으로 코딩을 하며 '신나게' 로그를 삽입하면 됩니다.

SDK > 하단 프레임 > Log

위의 메뉴와 친해지도록 하세요. :)


끝_