SAP 에서 BAPI 나 BDC 호출 후 에러를 확인하려고 보면, RETURN 테이블의 메시지 행이 평문이 아니라 메시지 ID + 번호 + 변수 형태로만 담겨 있어 그대로는 사용자에게 보여줄 수 없는 경우가 많습니다. 예시: ID = 'M7' · NUMBER = '060' · V1 = '10'. 사용자에게 "총 금액과 잔액이 일치하지 않습니다" 같은 완성된 문장을 보여주려면 이 메시지 ID + 번호로 메시지 마스터(T100) 에서 텍스트를 가져와 변수를 치환해야 합니다.
이 글에서는 BAPI 의 BAPIRET2 와 BDC 의 BDCMSGCOLL 메시지 구조를 비교하고, 3가지 메시지 빌드 방법 (MESSAGE ... INTO 구문 · FORMAT_MESSAGE FM · MESSAGE_TEXT_BUILD FM) 의 차이·활용·함정을 정리합니다. 실무에서 BAPI / BDC 인터페이스의 에러 로그 로직을 한 번에 표준화하는 데 사용 가능합니다.
핵심 — BAPIRET2 vs BDCMSGCOLL 비교
BAPI 와 BDC 는 에러를 담는 구조체가 다르지만 메시지 ID + 번호 + 변수 4개 라는 공통 골격을 공유합니다. 필드명만 약간 다를 뿐 빌드 방식은 동일합니다.
| 의미 | BAPIRET2 (BAPI) | BDCMSGCOLL (BDC) |
|---|---|---|
| 메시지 유형 | TYPE (E·W·S·I·A) |
MSGTYP |
| 메시지 클래스 (ID) | ID |
MSGID |
| 메시지 번호 | NUMBER |
MSGNR |
| 완성된 텍스트 (있을 때만) | MESSAGE |
없음 — 직접 빌드 필수 |
| 변수 1~4 | MESSAGE_V1 ~ V4 |
MSGV1 ~ MSGV4 |
핵심 차이는 BAPIRET2 에는 MESSAGE 필드가 있어 변환된 텍스트가 담겨 올 때도 있지만, BDCMSGCOLL 은 100% 직접 빌드 가 필요하다는 점입니다. BAPI 도 표준 / 비표준 구현에 따라 MESSAGE 가 비어 있는 경우가 자주 있어 항상 직접 빌드하는 패턴을 기본으로 잡는 것이 안전 합니다.
1단계 — MESSAGE INTO 구문 (가장 표준)
ABAP 표준의 MESSAGE ... INTO 구문을 사용하면 별도 FM 호출 없이 바로 메시지 텍스트를 빌드할 수 있습니다. SAP 표준에서 권장하는 방법입니다.
DATA: lv_msg TYPE string,
ls_return TYPE bapiret2.
LOOP AT lt_return INTO ls_return WHERE type CA 'EAW'.
MESSAGE ID ls_return-id
TYPE ls_return-type
NUMBER ls_return-number
WITH ls_return-message_v1
ls_return-message_v2
ls_return-message_v3
ls_return-message_v4
INTO lv_msg.
WRITE: / lv_msg.
ENDLOOP.
INTO 절을 붙이면 메시지를 화면에 띄우지 않고 변수에만 담깁니다. 인터페이스 / 배치에서 사용자에게 띄울 일이 없을 때 안전한 방식입니다.
장점 — ABAP 키워드라 별도 FM 의존성 없고, 메시지 클래스의 &1·&2·&3·&4 위치를 자동 치환.
2단계 — FORMAT_MESSAGE FM (가장 흔한 패턴)
레거시 코드에서 가장 자주 보이는 패턴. FORMAT_MESSAGE Function Module(Function Group SPOO) 을 호출합니다.
DATA: lv_msg TYPE string,
ls_return TYPE bapiret2.
LOOP AT lt_return INTO ls_return WHERE type CA 'EAW'.
CALL FUNCTION 'FORMAT_MESSAGE'
EXPORTING
id = ls_return-id
lang = sy-langu
no = ls_return-number
v1 = ls_return-message_v1
v2 = ls_return-message_v2
v3 = ls_return-message_v3
v4 = ls_return-message_v4
IMPORTING
msg = lv_msg
EXCEPTIONS
not_found = 1
OTHERS = 2.
IF sy-subrc = 0.
WRITE: / lv_msg.
ENDIF.
ENDLOOP.
내부에서 T100 메시지 마스터를 조회한 후 변수를 치환합니다. LANG 파라미터로 다국어 메시지를 가져올 수 있습니다('K' = 한국어 · 'E' = 영어). 기본값 '-D' 는 사용자 로그인 언어 + Default 순서로 시도합니다.
3단계 — BDC 에러 처리 (BDCMSGCOLL 패턴)
BDC 호출 후 CALL TRANSACTION ... MESSAGES INTO 로 메시지를 수집합니다.
DATA: lt_bdcdata TYPE TABLE OF bdcdata,
lt_msgcol TYPE TABLE OF bdcmsgcoll,
ls_msgcol TYPE bdcmsgcoll,
lv_msg TYPE string.
" ... BDC 데이터 빌드 ...
CALL TRANSACTION 'MM02'
USING lt_bdcdata
MODE 'N' " N = No display
UPDATE 'S' " S = Synchronous
MESSAGES INTO lt_msgcol.
LOOP AT lt_msgcol INTO ls_msgcol WHERE msgtyp CA 'EAW'.
MESSAGE ID ls_msgcol-msgid
TYPE ls_msgcol-msgtyp
NUMBER ls_msgcol-msgnr
WITH ls_msgcol-msgv1
ls_msgcol-msgv2
ls_msgcol-msgv3
ls_msgcol-msgv4
INTO lv_msg.
" 로그 적재
APPEND VALUE #(
type = ls_msgcol-msgtyp
message = lv_msg
msgid = ls_msgcol-msgid
msgnr = ls_msgcol-msgnr
) TO lt_error_log.
ENDLOOP.
핵심은 MESSAGES INTO 옵션을 빠뜨리면 메시지가 수집되지 않는다 는 점입니다. BDC 가 에러를 내도 호출자에게 전달되지 않으므로 디버깅이 어려워집니다. 모든 BDC 호출에는 이 옵션을 표준으로 박아두시기 바랍니다.
4단계 — BAPI RETURN 검증 + 메시지 빌드 표준 패턴
BAPI 호출 후 RETURN 을 일관성 있게 처리하는 표준 패턴.
DATA: lt_return TYPE TABLE OF bapiret2,
ls_return TYPE bapiret2,
lv_msg TYPE string,
lt_log TYPE TABLE OF ty_log,
ls_log TYPE ty_log,
lv_has_error TYPE abap_bool.
" ... BAPI 호출 ...
CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'
EXPORTING
headdata = ls_head
IMPORTING
return = ls_return " BAPI 마다 다름: 일부는 TABLES
TABLES
return = lt_return.
" 에러 / 워닝 모두 수집
LOOP AT lt_return INTO ls_return.
" 메시지 빌드
IF ls_return-message IS NOT INITIAL.
lv_msg = ls_return-message. " 이미 빌드된 텍스트가 있으면 그대로
ELSE.
MESSAGE ID ls_return-id
TYPE ls_return-type
NUMBER ls_return-number
WITH ls_return-message_v1
ls_return-message_v2
ls_return-message_v3
ls_return-message_v4
INTO lv_msg.
ENDIF.
" 로그 적재
ls_log-type = ls_return-type.
ls_log-msgid = ls_return-id.
ls_log-msgnr = ls_return-number.
ls_log-message = lv_msg.
APPEND ls_log TO lt_log.
" 에러 플래그
IF ls_return-type CA 'EA'.
lv_has_error = abap_true.
ENDIF.
ENDLOOP.
" 트랜잭션 처리
IF lv_has_error = abap_true.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING wait = 'X'.
ENDIF.
4가지 핵심 포인트:
MESSAGE필드가 채워져 있으면 그대로, 비어 있으면 빌드 (BAPI 마다 동작 다름)TYPE CA 'EA'로 에러 + 아보트만 잡고 워닝(W) 은 로그만 적재- ID + NUMBER 까지 로그에 함께 적재 — 메시지 추적용 (다음 단계 참고)
- 메시지 1줄이라도 에러면 즉시 ROLLBACK 하고 전체 트랜잭션 폐기
5단계 — 메시지 ID + 번호로 발생 위치 추적 (디버깅)
빌드된 메시지가 무엇을 의미하는지 모를 때 SAP 표준 디버거의 Message 브레이크포인트 로 정확한 발생 위치를 추적할 수 있습니다.
디버거 진입 (예: /h 입력 후 재실행)
→ Breakpoints → Breakpoint at → Message
→ Message Class: M7
→ Message Number: 060
→ F8 실행 → 그 메시지가 호출되는 표준 코드 라인에서 정지
이 방식은 "왜 이 에러가 발생하는지 모르겠을 때" 가장 빠른 진단 도구입니다. 표준 / 사내 코드 어디에서 메시지가 트리거되는지 정확한 라인이 보이므로 원인 분석이 가능합니다. BAPI / BDC 모두 동일하게 사용.
흔히 빠뜨리는 함정
MESSAGE 필드 빈 채로 사용
BAPI 마다 RETURN 의 MESSAGE 필드 채움 여부가 다릅니다. SAP 표준 BAPI 는 대부분 채워 보내지만, 일부 BAdI 가 호출 끝에 RETURN 만 채우고 MESSAGE 는 비우는 경우가 있습니다. MESSAGE 가 비었으면 빌드 패턴을 기본으로.
워닝 / 에러 혼동
TYPE 5가지 — E 에러, A 아보트, W 워닝, S 성공 / 상태, I 정보. ROLLBACK 트리거는 보통 E + A 만, 워닝을 에러로 잡으면 정상 트랜잭션까지 폐기됩니다.
BDC 호출에 MESSAGES INTO 누락
CALL TRANSACTION ... MESSAGES INTO 옵션 없이 호출하면 BDC 가 에러를 내도 호출자에게 전달되지 않습니다. 운영 중 BDC 가 조용히 실패하는 가장 흔한 원인.
MODE / UPDATE 옵션 조합
MODE 'N' (no display) + UPDATE 'S' (synchronous) 가 배치 표준. UPDATE 'A' (asynchronous) 는 호출은 빠르지만 직후 SELECT 시 데이터가 안 보일 수 있습니다.
FORMAT_MESSAGE 의 LANG '-D' 기본값
LANG = '-D' 는 "사용자 언어 → Default 언어 순서" 로 시도. 사용자 언어에 메시지 텍스트가 없으면 영문이 나옵니다. 한국어로 고정하려면 명시적으로 LANG = 'K' 전달.
변수 V1~V4 의 자리
메시지 텍스트의 &1·&2·&3·&4 자리에 변수가 치환됩니다. 메시지 클래스에 따라 & 한 개로만 표시되어 있을 수 있는데 그 경우 V1 부터 순서대로 치환됩니다. 변수 누락 시 빈 공간 / 이상한 텍스트로 보이므로 4개 모두 전달하는 것이 안전.
메시지 마스터에서 직접 조회 시도
T100 을 SELECT 해서 메시지 텍스트를 가져온 후 REPLACE '&1' WITH ... 처리하는 코드를 작성하는 경우가 있습니다. 작동은 하지만 표준 함수가 처리하는 멀티 변수 / 언어 fallback / 양식 변환을 다 따라가지 못합니다. 항상 표준 MESSAGE 구문 또는 FORMAT_MESSAGE 를 사용 하시기 바랍니다.
Long Text 가 필요한 경우
T100 의 짧은 텍스트만으로 부족할 때는 메시지의 Long Text(READ TEXT) 를 추가로 조회해야 합니다. SAP Help / 가이드 페이지의 메시지 설명도 이 Long Text 입니다.
전체 코드 — 복사용 통합본 (BAPI + BDC 메시지 로깅)
*&---------------------------------------------------------------------*
*& Report Z_XX_BAPI_BDC_MESSAGE_LOG
*&---------------------------------------------------------------------*
*& BAPI / BDC 호출 후 에러 메시지 빌드 + 로그 적재 샘플
*&---------------------------------------------------------------------*
REPORT z_xx_bapi_bdc_message_log.
TYPES: BEGIN OF ty_log,
type TYPE bapiret2-type,
msgid TYPE bapiret2-id,
msgnr TYPE bapiret2-number,
message TYPE string,
END OF ty_log.
DATA: lt_return TYPE TABLE OF bapiret2,
ls_return TYPE bapiret2,
lt_bdcdata TYPE TABLE OF bdcdata,
lt_msgcol TYPE TABLE OF bdcmsgcoll,
ls_msgcol TYPE bdcmsgcoll,
lv_msg TYPE string,
lt_log TYPE TABLE OF ty_log,
ls_log TYPE ty_log,
lv_has_error TYPE abap_bool.
* ★ 시나리오 1 — BAPI 호출 + 메시지 처리
" CALL FUNCTION 'BAPI_XXX' ... TABLES return = lt_return.
LOOP AT lt_return INTO ls_return.
" MESSAGE 필드가 채워졌으면 그대로, 아니면 빌드
IF ls_return-message IS NOT INITIAL.
lv_msg = ls_return-message.
ELSE.
MESSAGE ID ls_return-id
TYPE ls_return-type
NUMBER ls_return-number
WITH ls_return-message_v1
ls_return-message_v2
ls_return-message_v3
ls_return-message_v4
INTO lv_msg.
ENDIF.
ls_log-type = ls_return-type.
ls_log-msgid = ls_return-id.
ls_log-msgnr = ls_return-number.
ls_log-message = lv_msg.
APPEND ls_log TO lt_log.
IF ls_return-type CA 'EA'.
lv_has_error = abap_true.
ENDIF.
ENDLOOP.
IF lv_has_error = abap_true.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING wait = 'X'.
ENDIF.
* ★ 시나리오 2 — BDC 호출 + 메시지 처리
" ... BDC 데이터 빌드 ...
CALL TRANSACTION 'MM02'
USING lt_bdcdata
MODE 'N' " N = 화면 비표시
UPDATE 'S' " S = 동기
MESSAGES INTO lt_msgcol. " ★ 필수
CLEAR: lt_log, lv_has_error.
LOOP AT lt_msgcol INTO ls_msgcol.
MESSAGE ID ls_msgcol-msgid
TYPE ls_msgcol-msgtyp
NUMBER ls_msgcol-msgnr
WITH ls_msgcol-msgv1
ls_msgcol-msgv2
ls_msgcol-msgv3
ls_msgcol-msgv4
INTO lv_msg.
ls_log-type = ls_msgcol-msgtyp.
ls_log-msgid = ls_msgcol-msgid.
ls_log-msgnr = ls_msgcol-msgnr.
ls_log-message = lv_msg.
APPEND ls_log TO lt_log.
IF ls_msgcol-msgtyp CA 'EA'.
lv_has_error = abap_true.
ENDIF.
ENDLOOP.
* ★ 로그 출력
LOOP AT lt_log INTO ls_log.
WRITE: / ls_log-type, ls_log-msgid, ls_log-msgnr, ls_log-message.
ENDLOOP.
같이 보면 좋은 글: "BAdI 찾는 방법 5가지 — CL_EXITHANDLER 디버깅·SE84·SXS_ATTR 메타테이블" — 메시지가 사용자 BAdI 에서 발생할 때 어디서 호출되는지 추적하는 방법까지 함께 알아두면 디버깅이 훨씬 빨라집니다.
요약
| 방법 | 하는 일 | 권장 시나리오 |
|---|---|---|
| 1 | MESSAGE ... INTO 구문 |
표준 ABAP 방식 (가장 권장) |
| 2 | FORMAT_MESSAGE FM |
레거시 호환 / 다국어 강제 지정 시 |
| 3 | BAPIRET2-MESSAGE 그대로 |
필드가 채워진 BAPI 만 (위험) |
| 4 | Message 브레이크포인트 | 에러 발생 위치 추적 / 원인 분석 |
BAPI / BDC 의 에러 메시지 처리는 "메시지 ID + 번호 + 변수 4개" 패턴 만 정확히 이해하면 두 메커니즘이 동일한 방식으로 처리됩니다. MESSAGE ... INTO 구문을 기본으로 잡고, 로그에는 TYPE·ID·NUMBER·빌드된 텍스트를 함께 적재해두면 운영 중 에러 발생 시 즉시 원인 추적이 가능합니다. 표준 / 사내 BAdI 가 어디서 메시지를 던지는지 모를 때는 디버거의 Message Breakpoint 가 가장 빠른 진단 도구입니다.
Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다.
BAPIRET2 / BDCMSGCOLL 구조 · FORMAT_MESSAGE Function Module 시그니처 · T100 메시지 마스터 동작은 SAP NetWeaver 표준(ECC 6.0 / S/4HANA on-premise) 기준이며, 사내 BAPI / BAdI 커스터마이징에 따라 RETURN 필드 채움 정책이 다를 수 있으니 운영 시스템 적용 전 개발·QA 환경에서 검증하시기 바랍니다.
'디버깅 & 트러블슈팅' 카테고리의 다른 글
| [SAP ABAP] 레거시 RFC 호출 자동 로깅 — INCLUDE 2개 + 동적 ASSIGN 으로 IMPORT/EXPORT 캡처 (SYSTEM_CALLSTACK) (0) | 2026.05.19 |
|---|---|
| [SAP] CTS 이관 후 PERFORM 오브젝트 없음 에러 — SE80 Rebuild Object List로 해결 (0) | 2026.05.13 |
| [SAP ABAP] 디버깅 중 원하는 라인으로 점프 — Goto Statement 사용법 (0) | 2026.05.12 |
| [SAP ABAP] Watchpoint 디버깅 — 변수 값 변화 추적으로 대량 데이터 한 건만 잡기 (0) | 2026.05.12 |
| [SAP ABAP] Message 디버깅 — 에러 메시지가 어디서 발생하는지 추적하기 (0) | 2026.05.12 |