report-log — Core Pipeline & vtrc-api Request-Log Format
บทนี้ (branch: master) ครอบคลุมสองด้าน: (1) วิธีที่ app.js ประมวลผล และ (2) reverse-engineer รูปแบบ log จาก producer จริงใน vtrc-api
ส่วน A — Flow การประมวลผลใน report-log
app.js IIFE (app.js:208-230)
│ console.log(users.length) ← พิมพ์ 9486
│ dateStart = "2023-02-17"
│ dateEnd = "2023-02-18" ← hard-code ช่วง 1 วันเท่านั้นใน commit ปัจจุบัน
▼
while (dateStart !== dateEnd):
│ filename = `request_${dateStart}.log`
│ dateStart = moment(...).add(1, 'd')
│ data = await readData(filename)
│ listData.push(data)
▼
(CSV export ที่ app.js:221-229 ถูก comment ปิดทั้งหมด)
→ ผลสรุปอยู่ใน memory เท่านั้น
→ stdout ได้ console.log(dataM)/console.log(dataW) ซึ่งพิมพ์ raw log lines รวม PIIreadData → setData
readData(fileName) (app.js:85-88)
│ fs.readFile(fileName, 'utf-8')
▼
setData(content) (app.js:90-148)
│ split บรรทัดด้วย /(?:\r\n|\r|\n)/g
│ for แต่ละ pattern ใน array[] (31 รายการ):
│ dataM = lines ที่ include(mobile) && include(pattern)
│ dataW = lines ที่ include(web) && include(pattern)
│ ดึง userId ด้วย regex /"userId":"([^"]+)"/
│ JMM = users ∩ keyUserM → type "JM"
│ JWW = users ∩ keyUserW → type "JW"
│ OMM / OWW = users ที่ไม่อยู่ใน key set → "OM" / "OW"
│ ※ โค้ดปัจจุบัน filter จาก `users` ด้วย !includes
│ ทำให้ OMM/OWW เป็นสมาชิก users.json ที่ไม่ปรากฏในวันนั้น
│ ไม่ใช่ "user นอกจุฬาที่เรียก API" ตามชื่อคอมเมนต์เดิม
│ push { menu, user, type, date } ต่อ user (ไม่ dedupe ต่อวัน)
│ count++
▼
return listประเภทผลลัพธ์ (ตามชื่อในโค้ด)
| type | ความหมายตามคอมเมนต์ในโค้ด | Citation |
|---|---|---|
JM | จุฬา + Mobile | app.js:131-132 |
JW | จุฬา + Web | app.js:134-135 |
OM | Other + Mobile | app.js:137-138 |
OW | Other + Web | app.js:140-141 |
setVerticalData — dead path
อ่าน ./test-final.csv แล้วเขียน test-final-last.csv (app.js:150-206) — ไม่ถูกเรียกจาก IIFE มีความเสี่ยง index (json[out * i]) เมื่อขนาดข้อมูลไม่ตรง (app.js:197-200)
ส่วน B — Reverse-engineer: ใครเขียน request_*.log?
Pipeline ใน vtrc-api
Apollo GraphQL request
│
▼
GraphQLLogging.requestDidStart(requestContext)
│ `vtrc-api/api/src/lib/logging.js:12-17`
│ buildRequestContextObject → JSON
│ ข้าม IntrospectionQuery
▼
log.writeLog(`[INFO] ${JSON.stringify(logObj)}`, 'request')
│ `logging.js:16`
▼
wlog.writeLog(text, type='request')
│ `vtrc-api/api/src/lib/wlog.js:9-19`
│ path: {logDir}/../request_{yyyy}-{mm}-{dd}.log
│ prefix: `{th-TH locale datetime} - {text}\n`Response log แยกไฟล์ response_{date}.log (logging.js:20-33) — ไม่มีใน repo report-log (มีแค่ request)
ไม่มี morgan/winston ใน path นี้ — เป็น custom wlog + Apollo plugin
โครงสร้างบรรทัดที่ commit ใน report-log
{locale datetime} - [INFO] {JSON}\nตรงกับ format ที่ wlog.js:19 เขียน:
`${date.date.toLocaleString('th-th', { timeZone: TIME_ZONE_TEXT })} - ${text}\n`โดย text = [INFO] ${JSON.stringify(requestLogObj)}
JSON schema จาก producer (logging.js:65-70)
{
query: operationName หรือ query.replace(/\s\s+/g, ' '),
variables: copiedVariables, // อาจเป็น undefined/empty
headers: replaceHeaders,
from: { appId, userId }
}Redaction ที่ producer ทำ (และช่องโหว่)
| Layer | Keys ที่ถูกแทนด้วย _REPLACED_ | Citation |
|---|---|---|
variables | oldPassword, newPassword, confirmNewPassword, password, confirmPassword, pwd, passwd | logging.js:7,58 |
headers | authorization, apiKey | logging.js:8,59 |
ช่องโหว่ที่ยืนยันจากไฟล์ใน report-log:
- Header จริงใน log ใช้ชื่อ lowercase
apikey— ไม่ตรงกับapiKeyในรายการ redact → device key ยังโผล่เต็ม ในทุกบรรทัด - Inline GraphQL arguments ใน
querystring (เช่นlogin(password:"..."),sendVerifyCode(citizenId:"...")) ไม่ผ่านreplaceVariableValue→ plaintext เต็มในไฟล์ที่ commit
ส่วน C — เทียบ field: producer vs committed files
| Field | vtrc-api เขียน | มีใน report-log files? | Match? |
|---|---|---|---|
| line timestamp | ใช่ (wlog) | ใช่ | ใช่ |
[INFO] level | ใช่ | ใช่ (ไม่พบ [ERROR] ใน committed set ที่ตรวจ) | ใช่ |
query | ใช่ | ใช่ | ใช่ |
variables | ใช่ (เมื่อ client ส่ง) | ใช่ในบางบรรทัด | ใช่ |
headers | ใช่ | ใช่ | ใช่ |
from.appId | ใช่ | ใช่ | ใช่ |
from.userId | ใช่ (หรือ null) | ใช่ | ใช่ |
response_*.log | เขียนแยก | ไม่มีใน repo | N/A |
ส่วน D — สิ่งที่ app.js อ่านจากแต่ละบรรทัด
สคริปต์ไม่ได้ parse JSON เต็มรูป — ใช้ substring + regex:
| สิ่งที่ต้องการ | วิธีดึง | Citation |
|---|---|---|
| แยก web/mobile | item.includes(web) / includes(mobile) บน raw line | app.js:95-103 |
| แยกเมนู | item.includes(pattern) จาก array[] | app.js:95-96 |
| userId | regex "userId":"([^"]+)" | app.js:100-106 |
ไม่ได้ใช้ field from.appId, variables, หรือ parse header เป็น object — พอสำหรับรายงานนับเมนู แต่พิมพ์ raw lines ที่มี PII ออก stdout (app.js:108-109)
ส่วน E — Data flow กับระบบอื่น
vtrc-web / vtrc-mobile / backoffice
│ GraphQL + apikey + Authorization
▼
vtrc-api (GraphQLLogging → request_*.log บน disk ของ API host)
│ คนคัดลอกไฟล์ออกด้วยมือ (ไม่มี automation ใน report-log)
▼
report-log/request_*.log (committed)
│ node app.js
▼
in-memory list + stdout (CSV comment ปิด)ไม่มีการเชื่อม network จาก report-log ออกไป — ไม่มี axios/fetch/http ใน app.js