forked from aws/agentcore-cli
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdev-logger.ts
More file actions
163 lines (141 loc) · 5.1 KB
/
Copy pathdev-logger.ts
File metadata and controls
163 lines (141 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import { CLI_LOGS_DIR, CLI_SYSTEM_DIR, CONFIG_DIR, findConfigRoot } from '../../lib';
import { appendFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
import path from 'node:path';
export type DevLogLevel = 'info' | 'system' | 'warn' | 'error' | 'response';
/** Log levels that are considered "important" and written to file */
const IMPORTANT_LEVELS = new Set<DevLogLevel>(['system', 'warn', 'error', 'response']);
export interface DevLoggerOptions {
/** Base directory for agentcore/ (defaults to process.cwd()) */
baseDir?: string;
/** Agent name for log header */
agentName?: string;
/** Port the server is running on */
port?: number;
}
/**
* Logger for dev command execution.
* Creates log files in agentcore/.cli/logs/dev/ with timestamped filenames.
* Only logs important messages (system, warn, error, response) - filters out info noise.
*/
export class DevLogger {
readonly logFilePath: string;
private readonly startTime: Date;
constructor(options: DevLoggerOptions = {}) {
this.startTime = new Date();
// Find config root - require valid project to exist
const configRoot = options.baseDir ? path.join(options.baseDir, CONFIG_DIR) : findConfigRoot();
if (!configRoot || !existsSync(configRoot)) {
throw new Error('No agentcore project found. Cannot create dev logs.');
}
const logsDir = path.join(configRoot, CLI_SYSTEM_DIR, CLI_LOGS_DIR, 'dev');
// Ensure logs directory exists
if (!existsSync(logsDir)) {
mkdirSync(logsDir, { recursive: true });
}
// Generate timestamped filename: dev-YYYYMMDD-HHMMSS.log
const timestamp = this.formatTimestampForFilename(this.startTime);
this.logFilePath = path.join(logsDir, `dev-${timestamp}.log`);
// Write header
this.writeHeader(options);
}
/**
* Format a date for use in filename: YYYYMMDD-HHMMSS
*/
private formatTimestampForFilename(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}${month}${day}-${hours}${minutes}${seconds}`;
}
/**
* Format a date for log entries: HH:MM:SS
*/
private formatTime(date: Date = new Date()): string {
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
}
/**
* Write the log file header
*/
private writeHeader(options: DevLoggerOptions): void {
const separator = '='.repeat(80);
const agentLine = options.agentName ? `Agent: ${options.agentName}\n` : '';
const portLine = options.port ? `Port: ${options.port}\n` : '';
const header = `${separator}
AGENTCORE DEV LOG
${agentLine}${portLine}Started: ${this.startTime.toISOString()}
${separator}
`;
writeFileSync(this.logFilePath, header, 'utf-8');
}
/**
* Log a message. Only important levels (system, warn, error, response) are written to file.
*/
log(level: DevLogLevel, message: string): void {
// Only log important messages to file
if (!IMPORTANT_LEVELS.has(level)) {
return;
}
const levelPrefix = level === 'response' ? 'RESPONSE' : level.toUpperCase();
const line = `[${this.formatTime()}] [${levelPrefix}] ${message}`;
appendFileSync(this.logFilePath, line + '\n', 'utf-8');
}
/**
* Log a raw SSE event for debugging purposes.
* This logs the full SSE line as received from the server.
*/
logSSEEvent(rawLine: string): void {
const line = `[${this.formatTime()}] [SSE] ${rawLine}`;
appendFileSync(this.logFilePath, line + '\n', 'utf-8');
}
/**
* Finalize the log with a closing message
*/
finalize(): void {
const separator = '='.repeat(80);
const endTime = new Date();
const duration = endTime.getTime() - this.startTime.getTime();
const durationStr = this.formatDuration(duration);
const footer = `
${separator}
SESSION ENDED
Duration: ${durationStr}
${separator}
`;
appendFileSync(this.logFilePath, footer, 'utf-8');
}
/**
* Format duration in human-readable form
*/
private formatDuration(ms: number): string {
if (ms < 1000) {
return `${ms}ms`;
}
const seconds = ms / 1000;
if (seconds < 60) {
return `${seconds.toFixed(1)}s`;
}
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.round(seconds % 60);
return `${minutes}m ${remainingSeconds}s`;
}
/**
* Get a clickable terminal hyperlink to the log file.
*/
getClickableLogPath(): string {
const url = `file://${this.logFilePath}`;
const displayText = path.relative(process.cwd(), this.logFilePath);
return `\x1b]8;;${url}\x1b\\${displayText}\x1b]8;;\x1b\\`;
}
/**
* Get the relative path to the log file
*/
getRelativeLogPath(): string {
return path.relative(process.cwd(), this.logFilePath);
}
}