AI-Repo-Commander/Docs/diagrams/file_diagrams/logger.puml

439 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

' ===================================================================
' File: logger.puml
' Purpose: Single source of truth for module-level activity + per-method sequences.
' Module: logger.js — Structured logger with level gating, buffer, anti-spam utilities.
' Edit rules: Follow the legend at bottom; preserve VIEW/METHOD anchors for automation.
' ===================================================================
' Neutral defaults — typography/layout only (keeps partition colors intact)
skinparam Shadowing false
skinparam SequenceMessageAlign center
skinparam SequenceLifeLineBorderColor #666666
skinparam SequenceLifeLineBorderThickness 1
' ==== VIEW: Branch Flow (logger.js) ========================================
@startuml
title logger.js — Branch Flow (full module)
start
:logger;
fork
' -------- Logger.constructor() --------
partition "Logger.constructor()" #E7FAE3 {
:constructor;
:config = window.AI_REPO_CONFIG;\nbuffer=[]; loopCounts=Map(); startedAt=Date.now();
:setInterval(clean loopCounts if > 2× watchMs);
kill
}
fork again
' -------- error/warn/info/verbose/trace --------
partition "level helpers (error/warn/info/verbose/trace)" #FFF6D1 {
:error(msg,data) -> _log(1,'ERROR',...);
:warn(msg,data) -> _log(2,'WARN',...);
:info(msg,data) -> _log(3,'INFO',...);
:verbose(msg,data)-> _log(4,'VERBOSE',...);
:trace(msg,data) -> _log(5,'TRACE',...);
kill
}
fork again
' -------- command(action,status,extra) --------
partition "command(action, status, extra)" #FFE1DB {
:command;
:icon by status; info(`${icon} ${action} [${status}]`, extra);
kill
}
fork again
' -------- logLoop(kind, msg) --------
partition "logLoop(kind, msg)" #DCF9EE {
:logLoop;
:k=kind+msg; cur=loopCounts.get(k)||0;
:withinWatch = now-startedAt <= watchMs;
:if !withinWatch && kind!='WARN' -> return;
:if cur>=10 -> return;
:loopCounts.set(k,cur+1); suffix=(cur+1>1?` (${cur+1}x)`:'');
:route to error/warn/info(kind, msg+suffix);
kill
}
fork again
' -------- _log(levelNum, levelName, msg, data) --------
partition "_log(levelNum, levelName, msg, data)" #FFE6F0 {
:_log;
:enabled = !!config.debug.enabled;\nlevel = config.debug.level ?? 0;\nif !enabled or levelNum>level -> return;
:entry={ts, levelName, message, data:_sanitize(data)};
:buffer.push(entry); trim to debug.maxLines (default 400);
:console.log(`[AI RC ${levelName}]`, msg, optional sanitized data);
kill
}
fork again
' -------- _sanitize(data) --------
partition "_sanitize(data)" #E6F3FF {
:_sanitize;
:null -> null;\nHTMLElement -> "HTMLElement<TAG>";
:long string (>200) -> truncated + ellipsis;
:plain object -> shallow sanitize values recursively (HTMLElement/long string);
:else return data;
kill
}
fork again
' -------- getRecentLogs(n=50) --------
partition "getRecentLogs(n=50)" #F0E6FA {
:getRecentLogs;
:tail buffer n; map to lines: ISO LEVEL message {jsonData?}; join with \\n;
kill
}
fork again
' -------- setLevel(n) --------
partition "setLevel(n)" #E7FAF7 {
:setLevel;
:lv = clamp(n,0..5); config.set('debug.level', lv);\ninfo(`Log level => ${lv}`);
kill
}
fork again
' -------- bootstrap (global) --------
partition "bootstrap (global)" #FFF7E7 {
:window.AI_REPO_LOGGER = new Logger();
kill
}
end fork
@enduml
' ==== METHOD: Logger.constructor() =========================================
@startuml
title logger:constructor(): \n Prepare config, buffers, anti-spam counters, and cleanup timer
participant "Caller" as CL
participant "constructor()" as CTOR
participant "Config" as CFG
participant "Timer" as TMR
activate CL
CL -> CTOR : new Logger()
activate CTOR
CTOR -> CFG : window.AI_REPO_CONFIG
CFG --> CTOR : config
CTOR -> CTOR : buffer=[]; loopCounts=new Map(); startedAt=Date.now()
' periodic cleanup ~ every watchMs (default 120s)
CTOR -> CFG : get('debug.watchMs') || 120000
CFG --> CTOR : watchMs
CTOR -> TMR : setInterval(fn, watchMs)
TMR --> CTOR : id
CTOR -> CTOR : fn: if (Date.now()-startedAt > watchMs*2)\n loopCounts.clear(); startedAt=Date.now()
CTOR --> CL : instance
deactivate CTOR
deactivate CL
@enduml
' ==== METHOD: level helpers (error/warn/info/verbose/trace) ================
@startuml
title logger:level helpers: \n Route to _log() with appropriate numeric level
participant "Caller" as CL
participant "error()" as ERR
participant "warn()" as WRN
participant "info()" as INF
participant "verbose()" as VRB
participant "trace()" as TRC
participant "_log(...)" as LOG
activate CL
CL -> ERR : msg, data
ERR -> LOG : _log(1,'ERROR',msg,data)
LOG --> ERR : (void)
CL -> WRN : msg, data
WRN -> LOG : _log(2,'WARN',msg,data)
LOG --> WRN : (void)
CL -> INF : msg, data
INF -> LOG : _log(3,'INFO',msg,data)
LOG --> INF : (void)
CL -> VRB : msg, data
VRB -> LOG : _log(4,'VERBOSE',msg,data)
LOG --> VRB : (void)
CL -> TRC : msg, data
TRC -> LOG : _log(5,'TRACE',msg,data)
LOG --> TRC : (void)
TRC --> CL : done
deactivate TRC
deactivate CL
@enduml
' ==== METHOD: command(action, status, extra) ================================
@startuml
title logger:command(action, status, extra): \n Friendly lifecycle log with icon
participant "Caller" as CL
participant "command(action,status,extra)" as CMD
participant "info()" as INF
activate CL
CL -> CMD : action, status, extra
activate CMD
CMD -> CMD : icon = map(status)\n('👁️','📝','✓','⏳','⚙️','✅','❌','•')
CMD -> INF : info(`${icon} ${action} [${status}]`, extra)
INF --> CMD : (void)
CMD --> CL : (void)
deactivate CMD
deactivate CL
@enduml
' ==== METHOD: logLoop(kind, msg) ===========================================
@startuml
title logger:logLoop(kind, msg): \n Anti-spam logging for hot paths (max 10 per watch window)
participant "Caller" as CL
participant "logLoop(kind,msg)" as LLP
participant "Config" as CFG
participant "error()" as ERR
participant "warn()" as WRN
participant "info()" as INF
activate CL
CL -> LLP : kind, msg
activate LLP
LLP -> LLP : k=`${kind}:${msg}`; cur=loopCounts.get(k)||0
LLP -> CFG : get('debug.watchMs') || 120000
CFG --> LLP : watchMs
LLP -> LLP : withinWatch = (now-startedAt) <= watchMs
alt !withinWatch && kind != 'WARN'
LLP --> CL : (void)
deactivate LLP
deactivate CL
return
end
alt cur >= 10
LLP --> CL : (void)
deactivate LLP
deactivate CL
return
end
LLP -> LLP : loopCounts.set(k, cur+1);\nsuffix = (cur+1>1) ? ` (${cur+1}x)` : ''
alt kind == 'ERROR'
LLP -> ERR : error(msg+suffix)
ERR --> LLP : ok
else kind == 'WARN'
LLP -> WRN : warn(msg+suffix)
WRN --> LLP : ok
else
LLP -> INF : info(msg+suffix)
INF --> LLP : ok
end
LLP --> CL : (void)
deactivate LLP
deactivate CL
@enduml
' ==== METHOD: _log(levelNum, levelName, msg, data) =========================
@startuml
title logger:_log(levelNum, levelName, msg, data): \n Gate by config, buffer tail, print to console
participant "Caller" as CL
participant "_log(...)" as LOG
participant "Config" as CFG
participant "_sanitize(data)" as SAN
participant "Console" as CON
activate CL
CL -> LOG : levelNum, levelName, msg, data
activate LOG
LOG -> CFG : get('debug.enabled')
CFG --> LOG : enabled (bool)
LOG -> CFG : get('debug.level') ?? 0
CFG --> LOG : level (0..5)
alt !enabled or levelNum > level
LOG --> CL : (void)
deactivate LOG
deactivate CL
return
end
LOG -> SAN : _sanitize(data)
SAN --> LOG : safeData
LOG -> LOG : entry = {timestamp, level:levelName, message:String(msg), data:safeData}
LOG -> CFG : get('debug.maxLines') || 400
CFG --> LOG : maxLines
LOG -> LOG : buffer.push(entry); if buffer.length>maxLines -> splice head
' console output
alt with data
LOG -> CON : console.log(`[AI RC ${levelName}]`, msg, entry.data)
else no data
LOG -> CON : console.log(`[AI RC ${levelName}]`, msg)
end
CON --> LOG : printed
LOG --> CL : (void)
deactivate LOG
deactivate CL
@enduml
' ==== METHOD: _sanitize(data) ==============================================
@startuml
title logger:_sanitize(data): \n Redact DOM elements, truncate long strings, shallow-sanitize objects
participant "Caller" as CL
participant "_sanitize(data)" as SAN
participant "HTMLElement" as HTM
activate CL
CL -> SAN : data
activate SAN
alt !data
SAN --> CL : null
deactivate SAN
deactivate CL
return
end
alt data instanceof HTMLElement
SAN -> HTM : tagName
HTM --> SAN : TAG
SAN --> CL : "HTMLElement<TAG>"
deactivate SAN
deactivate CL
return
end
alt typeof data === 'string' && length > 200
SAN --> CL : data.slice(0,200) + '…'
deactivate SAN
deactivate CL
return
end
alt typeof data === 'object'
SAN -> SAN : out = {}
loop for each [k,v] of Object.entries(data)
alt v instanceof HTMLElement
SAN -> SAN : out[k] = `HTMLElement<${v.tagName}>`
else typeof v === 'string' && v.length>200
SAN -> SAN : out[k] = v.slice(0,200) + '…'
else
SAN -> SAN : out[k] = v
end
end
SAN --> CL : out
else other primitive
SAN --> CL : data
end
deactivate SAN
deactivate CL
@enduml
' ==== METHOD: getRecentLogs(n=50) ==========================================
@startuml
title logger:getRecentLogs(n): \n Tail buffer to plain-text lines for copy/export
participant "Caller" as CL
participant "getRecentLogs(n)" as GRL
activate CL
CL -> GRL : n (default 50)
activate GRL
GRL -> GRL : tail = buffer.slice(-n)
GRL -> GRL : lines = tail.map(e => `${e.timestamp} ${e.level.padEnd(7)} ${e.message}${e.data ? ' ' + JSON.stringify(e.data) : ''}`)
GRL --> CL : lines.join('\\n')
deactivate GRL
deactivate CL
@enduml
' ==== METHOD: setLevel(n) ==================================================
@startuml
title logger:setLevel(n): \n Clamp and persist numeric level, then self-log the change
participant "Caller" as CL
participant "setLevel(n)" as SLV
participant "Config" as CFG
participant "info()" as INF
activate CL
CL -> SLV : n
activate SLV
SLV -> SLV : lv = Math.max(0, Math.min(5, n))
SLV -> CFG : set('debug.level', lv)
CFG --> SLV : ok
SLV -> INF : info(`Log level => ${lv}`)
INF --> SLV : logged
SLV --> CL : (void)
deactivate SLV
deactivate CL
@enduml
' ==== METHOD: bootstrap (global) ===========================================
@startuml
title logger:bootstrap: \n Expose singleton logger on window
participant "Caller" as CL
participant "bootstrap" as BOOT
participant "Logger()" as CTOR
participant "window" as WIN
activate CL
CL -> BOOT : module load
activate BOOT
BOOT -> CTOR : new Logger()
CTOR --> BOOT : instance
BOOT -> WIN : window.AI_REPO_LOGGER = instance
WIN --> BOOT : ok
BOOT --> CL : (void)
deactivate BOOT
deactivate CL
@enduml
' ==== LEGEND ===============================================================
@startuml
legend bottom
== logger UML Style Guide (for future edits) ==
• Scope: One .puml per module. Keep two views:
(1) Activity "Branch Flow" for the whole module (partitions + soft colors),
(2) Per-function/Per-method Sequence diagrams for each exported or significant internal method.
• Sequence conventions:
1) First participant is the external caller (use "Caller" or a concrete origin).
2) Do NOT add a module/class lifeline; the name appears in the title only.
3) Include every directly-called helper/system as a participant
(e.g., "info()", "_log()", "_sanitize()", "Config", "Console", "Timer", "HTMLElement").
4) Prefer simple messages; Use --> for returns; -> for calls.
5) Use activate/deactivate as you see fit for clarity.
6) Use alt blocks only when branches meaningfully change the message flow.
• Activity view conventions:
A) Start with module node then fork partitions for each function/method.
B) One partition per function; soft background color; terminate branches with 'kill'.
C) Keep wording aligned with code (e.g., watchMs default 120000, maxLines default 400, 10x cap in logLoop).
• Color palette (soft pastels)
• Use --> for returns; -> for calls.
• Participants use quoted method names for internals; plain nouns for systems ("Config", "Console", "Timer").
• Keep this legend at the end of the file to standardize edits.
endlegend
@enduml