진단 로그 사용하기
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 ./logsexport PDCOMM_LOG=debugexport PDCOMM_LOG_FILE=./logs/pdcomm-client.jsonl# 이 셸에서 SDK 응용 프로그램이나 시뮬레이터를 시작합니다.New-Item -ItemType Directory -Force ./logs | Out-Null$env:PDCOMM_LOG = 'debug'$env:PDCOMM_LOG_FILE = './logs/pdcomm-client.jsonl'# 이 PowerShell에서 SDK 응용 프로그램이나 시뮬레이터를 시작합니다.같은 환경 변수를 SECS/GEM과 Modbus 라이브러리가 모두 읽습니다. 두 라이브러리를 한 프로세스에서 쓰면 두 로거가 같은 파일에 쓰고 회전하게 됩니다. 이 경우 환경 변수 대신 API로 라이브러리마다 다른 파일을 지정합니다. 클라이언트와 서버, 시뮬레이터처럼 프로세스가 다르면 파일도 따로 지정합니다.
API로 제어하기
섹션 제목: “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 라이브러리로 로그를 켜고, 요청 하나를 실행하고, 상태를 출력한 뒤 끄는 예제입니다.
#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;}using System.Runtime.InteropServices;if (args.Length != 2 || !Path.IsPathFullyQualified(args[0])) return 2;NativeLibrary.SetDllImportResolver(typeof(PDComm.Modbus.Endpoint).Assembly, (name, assembly, search) => name == "pdcomm_modbus_ffi" ? NativeLibrary.Load(args[0]) : IntPtr.Zero);PDComm.Modbus.DiagnosticLogging.Configure(new { enabled = true, level = "debug", file = args[1] });using (var endpoint = new PDComm.Modbus.Endpoint()) Console.WriteLine(endpoint.Call(new { action = "execute", pdu = new[] { 6, 0, 0, 0, 42 } }));PDComm.Modbus.DiagnosticLogging.Flush();Console.WriteLine(PDComm.Modbus.DiagnosticLogging.Status());PDComm.Modbus.DiagnosticLogging.Configure(new { enabled = false });Console.WriteLine(PDComm.Modbus.DiagnosticLogging.Status());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 꺼짐~5trace)이고 이름은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 설정입니다.
[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[PARSER] Name pdcomm_json Format jsonPath의*.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 로그