콘텐츠로 이동
무료 평가 시작

진단 로그 사용하기

PDComm 진단 로그는 기본으로 꺼져 있습니다. 이 페이지는 로그를 켜는 세 가지 방법과 수집기 연결을 설명합니다. 레코드 구조와 손실 규칙은 구조화된 진단 로그의 개념에서 설명합니다.

방법 언제 쓰나요
환경 변수 코드를 바꾸지 않고 이미 만든 프로그램이나 시뮬레이터의 로그를 켭니다.
API 프로그램이 실행 중에 로그를 켜고 끄거나 상태를 확인합니다.
시뮬레이터 동작 헤드리스, MCP, GUI 시뮬레이터의 로그를 실행 중에 바꿉니다.

라이브러리는 로거를 처음 쓸 때 환경 변수를 한 번 읽습니다. 프로그램이나 시뮬레이터를 시작하는 셸에서 설정합니다. 이미 실행 중인 프로그램에는 적용되지 않으므로 다시 시작합니다.

변수 값 기본값
PDCOMM_LOG error, warn, info, debug, trace 중 하나(소문자). 없거나 off면 로그를 켜지 않습니다. 없음(꺼짐)
PDCOMM_LOG_FILE 로그 파일 경로 없으면 표준 오류
PDCOMM_LOG_MAX_BYTES 회전 크기(8192~1073741824바이트) 10485760
PDCOMM_LOG_FILES 현재 파일을 포함해 남길 파일 수(1~16) 3
PDCOMM_LOG_FORENSICS 1, true, 0, false. debug나 trace에서만 켭니다. 꺼짐
터미널 창
mkdir -p ./logs
export PDCOMM_LOG=debug
export PDCOMM_LOG_FILE=./logs/pdcomm-client.jsonl
# 이 셸에서 SDK 응용 프로그램이나 시뮬레이터를 시작합니다.

같은 환경 변수를 SECS/GEM과 Modbus 라이브러리가 모두 읽습니다. 두 라이브러리를 한 프로세스에서 쓰면 두 로거가 같은 파일에 쓰고 회전하게 됩니다. 이 경우 환경 변수 대신 API로 라이브러리마다 다른 파일을 지정합니다. 클라이언트와 서버, 시뮬레이터처럼 프로세스가 다르면 파일도 따로 지정합니다.

설정은 JSON 객체 하나로 전달합니다. 모든 키는 선택이며 모르는 키는 거부합니다.

키 값 기본값
enabled true 또는 false true
level "error", "warn", "info", "debug", "trace" "info"
file 파일 경로 또는 null(표준 오류) null
max_bytes 8192~1073741824 10485760
files 1~16 3
forensics true 또는 false. debug나 trace에서만 켭니다. false

예를 들어 {"level":"debug","file":"./logs/sdk.jsonl"}는 로그를 켜고 debug 레벨로 파일에 씁니다. {"enabled":false}는 끕니다. 설정이 잘못되면 이전 설정을 유지합니다. 설정과 플러시는 로그 스레드의 응답을 최대 2초 기다리므로 통신 처리 경로 밖에서 호출합니다.

언어 SECS/GEM Modbus
C pdcomm_logging_configure, pdcomm_logging_status, pdcomm_logging_flush pdcomm_modbus_logging_configure, pdcomm_modbus_logging_status, pdcomm_modbus_logging_flush
C++ DiagnosticLogging (pdcomm_logging.hpp) DiagnosticLogging (pdcomm/modbus/pdcomm_modbus_logging.hpp)
.NET DiagnosticLogging DiagnosticLogging
  • C 함수는 UTF-8 JSON 바이트와 길이(최대 16384바이트)를 받습니다. 상태 함수는 출력 버퍼가 작으면 버퍼를 바꾸지 않고 필요한 크기를 알려 줍니다.
  • C++ 클래스는 정적 함수 configure, status, flush를 제공하고 실패하면 std::runtime_error를 던집니다.
  • .NET 클래스의 Configure는 익명 객체를 JSON으로 바꿔 전달합니다. Status는 JsonElement를 돌려줍니다. 실패하면 InvalidOperationException을 던집니다.

반환 코드는 다음과 같습니다.

함수 성공 잘못된 입력 버퍼 부족 내부 오류
pdcomm_logging_* 0 1 3 5
pdcomm_modbus_logging_* 0 1 4 3

Modbus 라이브러리로 로그를 켜고, 요청 하나를 실행하고, 상태를 출력한 뒤 끄는 예제입니다.

diagnostics/modbus/logging.cpp
#include <pdcomm/modbus/pdcomm_modbus.hpp>
#include <pdcomm/modbus/pdcomm_modbus_logging.hpp>
#include <iostream>
int main(int argc, char **argv) {
if (argc != 2) return 2;
try {
pdcomm::modbus::DiagnosticLogging::configure(argv[1]);
pdcomm::modbus::Endpoint endpoint;
std::cout << endpoint.call(R"({"action":"execute","pdu":[6,0,0,0,42]})") << '\n';
pdcomm::modbus::DiagnosticLogging::flush();
std::cout << pdcomm::modbus::DiagnosticLogging::status() << '\n';
pdcomm::modbus::DiagnosticLogging::configure(R"({"enabled":false})");
std::cout << pdcomm::modbus::DiagnosticLogging::status() << '\n';
} catch (const std::exception &error) {
std::cerr << error.what() << '\n';
return 1;
}
return 0;
}

전체 예제를 빌드하고 실행하는 과정은 진단 로그 남기고 읽기에 있습니다.

Modbus와 OPC UA 헤드리스 시뮬레이터는 다음 동작을 받습니다. MCP 서버에서는 modbus_logging_configure, opcua_logging_status처럼 제품 접두사가 붙은 도구 이름을 씁니다.

{"action":"logging_configure","config":{"enabled":true,"level":"debug","file":"./logs/modbus.jsonl"}}
{"action":"logging_status"}
{"action":"logging_flush"}
{"action":"logging_configure","config":{"enabled":false}}

logging_configure와 logging_status는 상태 객체를, logging_flush는 {"flushed":true}를 돌려줍니다. 설정이 잘못되면 다음 오류를 돌려줍니다.

오류 원인
Unknown logging option 모르는 키를 썼습니다.
Invalid logging level level 값이 다섯 가지 중 하나가 아닙니다.
Logging limits are out of range max_bytes나 files가 범위를 벗어났습니다.
Forensics requires debug or trace logging forensics를 낮은 레벨에서 켰습니다.
Invalid logging file file이 빈 문자열이거나 문자열이 아닙니다.
Cannot open logging sink 파일을 열 수 없습니다. 폴더와 권한을 확인합니다.

OPC UA 시뮬레이터 GUI에서는 Trace & Diagnostics 탭의 **Diagnostic log (off by default)**를 선택하고 파일 경로를 입력한 뒤 Apply logging을 누릅니다. 아래 줄에 상태와 written, dropped, write errors 값이 나타납니다.

{"build_id":"…","configuration_errors":0,"dropped":0,"enabled":true,"files":3,"forensic_sample_errors":0,"forensic_sample_interval_ms":1000,"forensic_samples":0,"forensics":false,"instance_id":"55856-1791996289686444000","level":4,"level_name":"debug","max_bytes":10485760,"queue_capacity":1024,"record_limit":8192,"schema_version":1,"sink":"file","write_errors":0,"written":5}
  • level은 숫자(0 꺼짐~5 trace)이고 이름은 level_name에 있습니다.
  • sink는 file 또는 stderr입니다. 파일 경로는 보여 주지 않습니다.
  • written, dropped, write_errors, configuration_errors로 손실과 오류를 확인합니다.
제품 component 주요 event
SECS/GEM ffi call_completed(상태와 경과 시간)
SECS/GEM hsms start, stop, state_changed, received, transaction_failed, protocol_error, transport_error
SECS/GEM hsms_transport socket_read, socket_write
SECS/GEM secs1 received, sent, send_failed, fault
Modbus ffi call_entered, lock_acquired, call_completed
Modbus service action_started, action_completed
Modbus tcp_client connected, submitted, socket_write, socket_read, completed, failed
Modbus tcp_server connection_accepted, request_received, response_queued, socket_write, connection_closed
Modbus serial io_batch, timeout
OPC UA 시뮬레이터 service action_started, action_completed

Modbus failed 사건의 fields에는 오류 종류(error_type), 시간 초과 여부, 대기 중인 요청, 받은 부분 응답 길이(rx_partial_bytes), 폴링 간격 최대값(max_poll_gap_us)이 있습니다. 응답이 아예 없었는지, 일부만 왔는지, SDK 처리가 늦었는지 가를 때 씁니다.

로그는 JSON Lines 파일이므로 Fluent Bit, Vector, OpenTelemetry Collector 같은 수집기가 그대로 읽습니다. 수집기는 SDK 실행에 필요하지 않으며 저장소도 정해져 있지 않습니다. 다음은 회전 파일을 읽어 표준 출력으로 보내는 Fluent Bit 설정입니다.

fluent-bit.conf
[SERVICE]
Flush 1
Parsers_File parsers.conf
[INPUT]
Name tail
Path /var/log/pdcomm/*.jsonl*
Tag pdcomm
Parser pdcomm_json
DB /var/lib/fluent-bit/pdcomm-offsets.db
DB.Sync Full
DB.Compare_Filename Off
Refresh_Interval 1
Rotate_Wait 30
Mem_Buf_Limit 10MB
Buffer_Chunk_Size 16k
Buffer_Max_Size 16k
Skip_Long_Lines Off
Read_From_Head On
[OUTPUT]
Name stdout
Match pdcomm
Format json_lines
Json_date_key ingested_at
Json_date_format double
parsers.conf
[PARSER]
Name pdcomm_json
Format json
  • Path의 *.jsonl*는 회전한 .1, .2 파일도 읽습니다. DB는 읽은 위치를 저장하므로 수집기를 다시 시작해도 중복이나 누락이 생기지 않습니다. 이 파일을 지우면 처음부터 다시 읽습니다.
  • Buffer_Max_Size 16k는 레코드 최대 크기(8192바이트)보다 큽니다.
  • ingested_at은 수집 시각(UTC epoch 초)입니다. SDK의 사건 시각은 레코드의 timestamp_unix_ms에 그대로 남습니다. 두 시각을 혼동하지 않습니다.
  • 수집기를 강제로 끝내거나 보관 용량을 넘기면 누락이나 중복이 생길 수 있습니다. instance_id와 sequence로 대조합니다.

Grafana Loki에 저장한다면 protocol, level처럼 값의 종류가 적은 필드만 레이블(label)로 씁니다. request_id, connection_id, sequence처럼 값이 계속 늘어나는 필드는 JSON 필드로 검색합니다(Loki 레이블 안내). Fluent Bit 설정의 세부 항목은 Tail 입력 공식 문서에서 사용하는 버전과 대조합니다.

문제를 문의할 때는 다음을 함께 보냅니다. 고객 데이터 원문은 넣지 않습니다.

  • SDK 버전과 build_id, 운영체제와 CPU 아키텍처
  • 실패한 시각과 instance_id, request_id, connection_id
  • 원래 오류 메시지와 반환 코드
  • 상태의 dropped, write_errors, configuration_errors
  • 필요한 부분만 남긴 JSONL 로그