@archznn/xavva 3.1.2 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -12
- package/package.json +3 -2
- package/src/commands/AuditCommand.ts +12 -10
- package/src/commands/BuildCommand.ts +9 -7
- package/src/commands/ChangelogCommand.ts +5 -5
- package/src/commands/CleanCommand.ts +242 -0
- package/src/commands/CompletionCommand.ts +7 -7
- package/src/commands/DbCommand.ts +43 -14
- package/src/commands/DeployCommand.ts +252 -229
- package/src/commands/DepsCommand.ts +174 -174
- package/src/commands/DockerCommand.ts +35 -4
- package/src/commands/DoctorCommand.ts +252 -239
- package/src/commands/EncodingCommand.ts +26 -19
- package/src/commands/HealthCommand.ts +7 -7
- package/src/commands/HelpCommand.ts +34 -14
- package/src/commands/HistoryCommand.ts +5 -5
- package/src/commands/HttpCommand.ts +27 -1
- package/src/commands/IdeCommand.ts +313 -0
- package/src/commands/InitCommand.ts +26 -25
- package/src/commands/LogsCommand.ts +8 -6
- package/src/commands/ProfilesCommand.ts +6 -6
- package/src/commands/RedoCommand.ts +2 -2
- package/src/commands/RunCommand.ts +64 -24
- package/src/commands/StartCommand.ts +9 -7
- package/src/commands/TestCommand.ts +25 -1
- package/src/commands/TomcatCommand.ts +232 -88
- package/src/config/versions.ts +111 -9
- package/src/di/container.ts +239 -105
- package/src/errors/ErrorHandler.ts +23 -19
- package/src/errors/errorMessages.ts +235 -0
- package/src/index.ts +20 -6
- package/src/logging/FileLogger.ts +235 -0
- package/src/logging/Logger.ts +545 -0
- package/src/logging/OperationLogger.ts +296 -0
- package/src/logging/ProgressLogger.ts +187 -0
- package/src/logging/TableLogger.ts +246 -0
- package/src/logging/colors.ts +167 -0
- package/src/logging/constants.ts +176 -0
- package/src/logging/formatters.ts +337 -0
- package/src/logging/index.ts +93 -0
- package/src/logging/types.ts +64 -0
- package/src/plugins/PluginManager.ts +325 -0
- package/src/plugins/types.ts +82 -0
- package/src/services/AuditService.ts +5 -3
- package/src/services/BuildService.ts +15 -17
- package/src/services/DashboardService.ts +14 -3
- package/src/services/DbService.ts +35 -34
- package/src/services/DependencyAnalyzerService.ts +18 -18
- package/src/services/DependencyCacheService.ts +303 -0
- package/src/services/DeployWatcher.ts +127 -23
- package/src/services/DockerService.ts +3 -3
- package/src/services/EmbeddedTomcatService.ts +13 -12
- package/src/services/FileWatcher.ts +15 -7
- package/src/services/HttpService.ts +5 -5
- package/src/services/LogAnalyzer.ts +26 -22
- package/src/services/PerformanceProfiler.ts +267 -0
- package/src/services/ProjectService.ts +3 -0
- package/src/services/TestService.ts +3 -3
- package/src/services/TomcatService.ts +46 -25
- package/src/services/tomcat/TomcatBackupManager.ts +330 -0
- package/src/services/tomcat/TomcatChecksumVerifier.ts +211 -0
- package/src/services/tomcat/TomcatCompatibilityChecker.ts +298 -0
- package/src/services/tomcat/TomcatDownloadCache.ts +250 -0
- package/src/services/tomcat/TomcatDownloadService.ts +335 -0
- package/src/services/tomcat/TomcatInstallerService.ts +474 -0
- package/src/services/tomcat/TomcatMirrorManager.ts +181 -0
- package/src/services/tomcat/index.ts +36 -0
- package/src/services/tomcat/types.ts +120 -0
- package/src/types/args.ts +68 -1
- package/src/types/configSchema.ts +174 -0
- package/src/utils/ChangelogGenerator.ts +11 -11
- package/src/utils/LoggerLevel.ts +44 -20
- package/src/utils/ProgressBar.ts +87 -46
- package/src/utils/argsParser.ts +260 -0
- package/src/utils/config.ts +340 -189
- package/src/utils/constants.ts +87 -9
- package/src/utils/dryRun.ts +192 -0
- package/src/utils/processManager.ts +23 -7
- package/src/utils/security.ts +293 -0
- package/src/utils/ui.ts +299 -428
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mensagens de erro contextuais com sugestões e auto-fix
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Logger } from "../logging";
|
|
6
|
+
|
|
7
|
+
export interface ErrorContext {
|
|
8
|
+
message: string;
|
|
9
|
+
suggestion: string;
|
|
10
|
+
docsUrl?: string;
|
|
11
|
+
autoFix?: () => Promise<boolean>;
|
|
12
|
+
command?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const ErrorMessages: Record<string, ErrorContext> = {
|
|
16
|
+
// Build errors
|
|
17
|
+
MAVEN_NOT_FOUND: {
|
|
18
|
+
message: "Maven não encontrado no PATH",
|
|
19
|
+
suggestion: "Instale o Maven ou adicione ao PATH\n" +
|
|
20
|
+
" • Windows: choco install maven\n" +
|
|
21
|
+
" • macOS: brew install maven\n" +
|
|
22
|
+
" • Linux: sudo apt install maven",
|
|
23
|
+
docsUrl: "https://maven.apache.org/install.html",
|
|
24
|
+
command: "xavva doctor --fix",
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
GRADLE_NOT_FOUND: {
|
|
28
|
+
message: "Gradle não encontrado no PATH",
|
|
29
|
+
suggestion: "Instale o Gradle ou adicione ao PATH\n" +
|
|
30
|
+
" • Windows: choco install gradle\n" +
|
|
31
|
+
" • macOS: brew install gradle\n" +
|
|
32
|
+
" • Linux: sdk install gradle",
|
|
33
|
+
docsUrl: "https://gradle.org/install/",
|
|
34
|
+
command: "xavva doctor --fix",
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
BUILD_FAILED: {
|
|
38
|
+
message: "Build falhou",
|
|
39
|
+
suggestion: "Use --verbose para ver detalhes completos\n" +
|
|
40
|
+
"Verifique se todas as dependências estão disponíveis",
|
|
41
|
+
command: "xavva build --verbose",
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// Tomcat errors
|
|
45
|
+
TOMCAT_NOT_FOUND: {
|
|
46
|
+
message: "Tomcat não encontrado",
|
|
47
|
+
suggestion: "Defina TOMCAT_HOME ou use Tomcat embutido\n" +
|
|
48
|
+
"Execute 'xavva dev --yes' para instalação automática",
|
|
49
|
+
command: "xavva dev --yes",
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
TOMCAT_PORT_IN_USE: {
|
|
53
|
+
message: "Porta {port} já está em uso",
|
|
54
|
+
suggestion: "Escolha outra porta ou pare o processo atual",
|
|
55
|
+
command: "xavva dev --port 8081",
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Java errors
|
|
59
|
+
JAVA_NOT_FOUND: {
|
|
60
|
+
message: "Java não encontrado no PATH",
|
|
61
|
+
suggestion: "Instale JDK 11+ ou defina JAVA_HOME\n" +
|
|
62
|
+
"Execute 'xavva doctor --fix' para configuração automática",
|
|
63
|
+
command: "xavva doctor --fix",
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
JAVA_VERSION_TOO_OLD: {
|
|
67
|
+
message: "Versão do Java muito antiga ({version})",
|
|
68
|
+
suggestion: "JDK 11+ é necessário\n" +
|
|
69
|
+
"Execute 'xavva doctor --fix' para instalar JDK moderno",
|
|
70
|
+
command: "xavva doctor --fix",
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Config errors
|
|
74
|
+
CONFIG_INVALID: {
|
|
75
|
+
message: "Configuração inválida em xavva.json",
|
|
76
|
+
suggestion: "Verifique sintaxe JSON e campos obrigatórios\n" +
|
|
77
|
+
"Execute 'xavva config' para ver configuração atual",
|
|
78
|
+
command: "xavva config",
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
// Network errors
|
|
82
|
+
DOWNLOAD_FAILED: {
|
|
83
|
+
message: "Falha no download",
|
|
84
|
+
suggestion: "Verifique conexão de internet\n" +
|
|
85
|
+
"Tente novamente em alguns instantes",
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
TIMEOUT: {
|
|
89
|
+
message: "Tempo de espera esgotado",
|
|
90
|
+
suggestion: "Operação demorou muito tempo\n" +
|
|
91
|
+
"Verifique conexão ou tente aumentar o timeout",
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// File system errors
|
|
95
|
+
PERMISSION_DENIED: {
|
|
96
|
+
message: "Permissão negada",
|
|
97
|
+
suggestion: "Execute com permissões adequadas\n" +
|
|
98
|
+
"Windows: Execute como Administrador\n" +
|
|
99
|
+
"Linux/macOS: Use sudo se necessário",
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
DISK_FULL: {
|
|
103
|
+
message: "Disco cheio",
|
|
104
|
+
suggestion: "Libere espaço em disco\n" +
|
|
105
|
+
"Execute 'xavva clean' para limpar arquivos temporários",
|
|
106
|
+
command: "xavva clean",
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// Port errors
|
|
110
|
+
PORT_UNAVAILABLE: {
|
|
111
|
+
message: "Porta não disponível",
|
|
112
|
+
suggestion: "Porta pode estar em uso ou bloqueada pelo firewall\n" +
|
|
113
|
+
"Use outra porta: xavva dev --port 8081",
|
|
114
|
+
command: "xavva dev --port 8081",
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// Watch errors
|
|
118
|
+
WATCH_TOO_MANY_FILES: {
|
|
119
|
+
message: "Muitos arquivos para monitorar",
|
|
120
|
+
suggestion: "Exclua node_modules do watch\n" +
|
|
121
|
+
"Adicione ao .gitignore ou use --exclude",
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Obtém mensagem de erro contextual
|
|
127
|
+
*/
|
|
128
|
+
export function getErrorMessage(code: string, params: Record<string, string> = {}): ErrorContext {
|
|
129
|
+
const template = ErrorMessages[code] || {
|
|
130
|
+
message: `Erro desconhecido: ${code}`,
|
|
131
|
+
suggestion: "Consulte a documentação ou reporte o problema",
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Substitui parâmetros
|
|
135
|
+
let message = template.message;
|
|
136
|
+
for (const [key, value] of Object.entries(params)) {
|
|
137
|
+
message = message.replace(`{${key}}`, value);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
...template,
|
|
142
|
+
message,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Mostra erro com contexto
|
|
148
|
+
*/
|
|
149
|
+
export function showContextualError(code: string, params: Record<string, string> = {}): void {
|
|
150
|
+
const logger = Logger.getInstance();
|
|
151
|
+
const error = getErrorMessage(code, params);
|
|
152
|
+
|
|
153
|
+
logger.error(error.message);
|
|
154
|
+
|
|
155
|
+
if (error.suggestion) {
|
|
156
|
+
logger.newline();
|
|
157
|
+
logger.info("💡 Dica:");
|
|
158
|
+
for (const line of error.suggestion.split('\n')) {
|
|
159
|
+
logger.info(` ${line}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (error.command) {
|
|
164
|
+
logger.newline();
|
|
165
|
+
logger.info(`🔧 Tente: ${error.command}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (error.docsUrl) {
|
|
169
|
+
logger.newline();
|
|
170
|
+
logger.url("📖 Documentação", error.docsUrl);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Tenta executar auto-fix para um erro
|
|
176
|
+
*/
|
|
177
|
+
export async function tryAutoFix(code: string): Promise<boolean> {
|
|
178
|
+
const error = ErrorMessages[code];
|
|
179
|
+
|
|
180
|
+
if (!error?.autoFix) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const logger = Logger.getInstance();
|
|
185
|
+
logger.info(`🔧 Tentando correção automática...`);
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
return await error.autoFix();
|
|
189
|
+
} catch (e) {
|
|
190
|
+
logger.error(`Auto-fix falhou: ${(e as Error).message}`);
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Detecta código de erro baseado na mensagem
|
|
197
|
+
*/
|
|
198
|
+
export function detectErrorCode(message: string): string | null {
|
|
199
|
+
const patterns: Record<string, RegExp[]> = {
|
|
200
|
+
MAVEN_NOT_FOUND: [/mvn[\s\w]*not found/i, /maven.*not.*found/i, /cannot find.*mvn/i],
|
|
201
|
+
GRADLE_NOT_FOUND: [/gradle[\s\w]*not found/i, /cannot find.*gradle/i],
|
|
202
|
+
JAVA_NOT_FOUND: [/java[\s\w]*not found/i, /cannot find.*java/i, /no java/i],
|
|
203
|
+
BUILD_FAILED: [/build failed/i, /compilation failed/i, /failed to compile/i],
|
|
204
|
+
TOMCAT_PORT_IN_USE: [/port.*in use/i, /address already in use/i, /bind.*failed/i],
|
|
205
|
+
PERMISSION_DENIED: [/permission denied/i, /access denied/i, /eacces/i],
|
|
206
|
+
DISK_FULL: [/disk full/i, /no space left/i, /enospc/i],
|
|
207
|
+
TIMEOUT: [/timeout/i, /timed out/i, /etimedout/i],
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
for (const [code, regexes] of Object.entries(patterns)) {
|
|
211
|
+
for (const regex of regexes) {
|
|
212
|
+
if (regex.test(message)) {
|
|
213
|
+
return code;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Mostra erro genérico com detecção automática
|
|
223
|
+
*/
|
|
224
|
+
export function showSmartError(error: Error | string): void {
|
|
225
|
+
const message = error instanceof Error ? error.message : error;
|
|
226
|
+
const code = detectErrorCode(message);
|
|
227
|
+
|
|
228
|
+
if (code) {
|
|
229
|
+
showContextualError(code);
|
|
230
|
+
} else {
|
|
231
|
+
const logger = Logger.getInstance();
|
|
232
|
+
logger.error(message);
|
|
233
|
+
logger.info("Use --verbose para mais detalhes ou consulte a documentação");
|
|
234
|
+
}
|
|
235
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { ErrorHandler } from "./errors/ErrorHandler";
|
|
|
7
7
|
import { ProcessManager } from "./utils/processManager";
|
|
8
8
|
import { LoggerLevel } from "./utils/LoggerLevel";
|
|
9
9
|
import pkg from "../package.json";
|
|
10
|
-
import { Logger } from "./utils/ui";
|
|
10
|
+
import { Logger, C } from "./utils/ui";
|
|
11
11
|
import type { CLIArguments } from "./types/args";
|
|
12
12
|
|
|
13
13
|
async function main() {
|
|
@@ -32,7 +32,7 @@ async function main() {
|
|
|
32
32
|
"debug", "logs", "docs", "audit", "profiles",
|
|
33
33
|
"deps", "tomcat", "encoding", "init", "config",
|
|
34
34
|
"history", "redo", "health", "completion", "changelog",
|
|
35
|
-
"test", "db", "http", "docker", "help"
|
|
35
|
+
"test", "db", "http", "docker", "help", "clean", "ide"
|
|
36
36
|
];
|
|
37
37
|
const commandName = positionals.find(p => commandNames.includes(p)) || "deploy";
|
|
38
38
|
|
|
@@ -49,9 +49,15 @@ async function main() {
|
|
|
49
49
|
|
|
50
50
|
// Handler de help
|
|
51
51
|
if (values.help) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
// Se for help de comando específico, deixa o comando tratar
|
|
53
|
+
if (commandName !== "help" && commandName !== "deploy") {
|
|
54
|
+
// O comando específico vai tratar o --help
|
|
55
|
+
} else {
|
|
56
|
+
// Help geral
|
|
57
|
+
const { HelpCommand } = await import("./commands/HelpCommand");
|
|
58
|
+
new HelpCommand().execute(config, values as CLIArguments);
|
|
59
|
+
await processManager.shutdown(0);
|
|
60
|
+
}
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
// Inicializa Container de DI
|
|
@@ -71,7 +77,7 @@ async function main() {
|
|
|
71
77
|
if (values.tui) {
|
|
72
78
|
const deployCmd = commands.deploy;
|
|
73
79
|
services.dashboardService.onAction("r", () => {
|
|
74
|
-
services.dashboardService.log(
|
|
80
|
+
services.dashboardService.log(C.warning + "Restart manual solicitado via TUI...");
|
|
75
81
|
deployCmd.execute(config, { watch: true, incremental: false });
|
|
76
82
|
});
|
|
77
83
|
}
|
|
@@ -116,6 +122,8 @@ async function main() {
|
|
|
116
122
|
registry.register("db", commands.db);
|
|
117
123
|
registry.register("http", commands.http);
|
|
118
124
|
registry.register("docker", commands.docker);
|
|
125
|
+
registry.register("clean", commands.clean);
|
|
126
|
+
registry.register("ide", commands.ide);
|
|
119
127
|
|
|
120
128
|
// Configura flags específicas
|
|
121
129
|
if (commandName === "debug") values.debug = true;
|
|
@@ -158,6 +166,12 @@ async function main() {
|
|
|
158
166
|
}
|
|
159
167
|
}
|
|
160
168
|
}
|
|
169
|
+
|
|
170
|
+
// Só encerra o processo se não estiver em modo watch ou TUI
|
|
171
|
+
// (esses modos precisam continuar rodando)
|
|
172
|
+
if (!values.watch && !values.tui) {
|
|
173
|
+
await ProcessManager.getInstance().shutdown(0);
|
|
174
|
+
}
|
|
161
175
|
}
|
|
162
176
|
|
|
163
177
|
// Entry point com tratamento global de erros
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileLogger - Persistência de logs em arquivo
|
|
3
|
+
*
|
|
4
|
+
* Salva logs em formato JSON Lines (.jsonl) para fácil integração
|
|
5
|
+
* com ferramentas de análise como ELK, Datadog, etc.
|
|
6
|
+
*
|
|
7
|
+
* Características:
|
|
8
|
+
* - Rotação automática por data
|
|
9
|
+
* - Limite de tamanho por arquivo
|
|
10
|
+
* - Limite de arquivos retidos
|
|
11
|
+
* - Formato estruturado (JSON)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, mkdirSync, appendFileSync, readdirSync, statSync, unlinkSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
import type { LogEntry } from './types';
|
|
17
|
+
import { FILE_LOG } from './constants';
|
|
18
|
+
|
|
19
|
+
export interface FileLoggerOptions {
|
|
20
|
+
logDir: string;
|
|
21
|
+
maxFiles: number;
|
|
22
|
+
maxSize: number;
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class FileLogger {
|
|
27
|
+
private options: FileLoggerOptions;
|
|
28
|
+
private currentFile: string | null = null;
|
|
29
|
+
private currentSize = 0;
|
|
30
|
+
private initialized = false;
|
|
31
|
+
|
|
32
|
+
constructor(options: Partial<FileLoggerOptions> = {}) {
|
|
33
|
+
this.options = {
|
|
34
|
+
logDir: '.xavva/logs',
|
|
35
|
+
maxFiles: FILE_LOG.maxFiles,
|
|
36
|
+
maxSize: FILE_LOG.maxSize,
|
|
37
|
+
enabled: true,
|
|
38
|
+
...options,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Inicializa o diretório de logs
|
|
44
|
+
*/
|
|
45
|
+
private initialize(): void {
|
|
46
|
+
if (this.initialized || !this.options.enabled) return;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
if (!existsSync(this.options.logDir)) {
|
|
50
|
+
mkdirSync(this.options.logDir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
this.initialized = true;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('Falha ao inicializar diretório de logs:', error);
|
|
55
|
+
this.options.enabled = false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Obtém o nome do arquivo de log atual
|
|
61
|
+
*/
|
|
62
|
+
private getCurrentFileName(): string {
|
|
63
|
+
const date = new Date();
|
|
64
|
+
const year = date.getFullYear();
|
|
65
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
66
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
67
|
+
return join(this.options.logDir, `xavva-${year}-${month}-${day}.jsonl`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Verifica se precisa rotacionar para novo arquivo
|
|
72
|
+
*/
|
|
73
|
+
private checkRotation(): void {
|
|
74
|
+
const newFile = this.getCurrentFileName();
|
|
75
|
+
|
|
76
|
+
if (this.currentFile !== newFile) {
|
|
77
|
+
// Mudou de dia, atualiza arquivo
|
|
78
|
+
this.currentFile = newFile;
|
|
79
|
+
this.currentSize = existsSync(newFile) ? statSync(newFile).size : 0;
|
|
80
|
+
this.cleanupOldFiles();
|
|
81
|
+
} else if (this.currentFile && this.currentSize > this.options.maxSize) {
|
|
82
|
+
// Arquivo muito grande, cria novo com sufixo
|
|
83
|
+
const timestamp = Date.now();
|
|
84
|
+
this.currentFile = this.currentFile.replace('.jsonl', `-${timestamp}.jsonl`);
|
|
85
|
+
this.currentSize = 0;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Remove arquivos de log antigos
|
|
91
|
+
*/
|
|
92
|
+
private cleanupOldFiles(): void {
|
|
93
|
+
try {
|
|
94
|
+
if (!existsSync(this.options.logDir)) return;
|
|
95
|
+
|
|
96
|
+
const files = readdirSync(this.options.logDir)
|
|
97
|
+
.filter(f => f.startsWith('xavva-') && f.endsWith('.jsonl'))
|
|
98
|
+
.map(f => ({
|
|
99
|
+
name: f,
|
|
100
|
+
path: join(this.options.logDir, f),
|
|
101
|
+
time: statSync(join(this.options.logDir, f)).mtime.getTime(),
|
|
102
|
+
}))
|
|
103
|
+
.sort((a, b) => b.time - a.time); // Mais recente primeiro
|
|
104
|
+
|
|
105
|
+
// Remove arquivos excedentes
|
|
106
|
+
const filesToRemove = files.slice(this.options.maxFiles);
|
|
107
|
+
for (const file of filesToRemove) {
|
|
108
|
+
try {
|
|
109
|
+
unlinkSync(file.path);
|
|
110
|
+
} catch {
|
|
111
|
+
// Ignora erros de deleção
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// Ignora erros de cleanup
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Escreve uma entrada de log no arquivo
|
|
121
|
+
*/
|
|
122
|
+
write(entry: LogEntry): void {
|
|
123
|
+
if (!this.options.enabled) return;
|
|
124
|
+
|
|
125
|
+
this.initialize();
|
|
126
|
+
this.checkRotation();
|
|
127
|
+
|
|
128
|
+
if (!this.currentFile) return;
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
// Formata como JSON Lines
|
|
132
|
+
const logLine = JSON.stringify({
|
|
133
|
+
timestamp: entry.timestamp.toISOString(),
|
|
134
|
+
level: entry.level,
|
|
135
|
+
message: entry.message.replace(/\x1b\[\d+m/g, ''), // Remove ANSI
|
|
136
|
+
traceId: entry.context?.traceId,
|
|
137
|
+
metadata: entry.metadata,
|
|
138
|
+
}) + '\n';
|
|
139
|
+
|
|
140
|
+
appendFileSync(this.currentFile, logLine);
|
|
141
|
+
this.currentSize += Buffer.byteLength(logLine);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
// Falha silenciosa - não deve quebrar a aplicação por causa de log em arquivo
|
|
144
|
+
if (process.env.XAVVA_DEBUG) {
|
|
145
|
+
console.error('Falha ao escrever log em arquivo:', error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Habilita/desabilita logging em arquivo
|
|
152
|
+
*/
|
|
153
|
+
setEnabled(enabled: boolean): void {
|
|
154
|
+
this.options.enabled = enabled;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Verifica se está habilitado
|
|
159
|
+
*/
|
|
160
|
+
isEnabled(): boolean {
|
|
161
|
+
return this.options.enabled;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Obtém caminho do diretório de logs
|
|
166
|
+
*/
|
|
167
|
+
getLogDir(): string {
|
|
168
|
+
return this.options.logDir;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Lista arquivos de log existentes
|
|
173
|
+
*/
|
|
174
|
+
listLogFiles(): { name: string; path: string; size: number; modified: Date }[] {
|
|
175
|
+
try {
|
|
176
|
+
if (!existsSync(this.options.logDir)) return [];
|
|
177
|
+
|
|
178
|
+
return readdirSync(this.options.logDir)
|
|
179
|
+
.filter(f => f.startsWith('xavva-') && f.endsWith('.jsonl'))
|
|
180
|
+
.map(f => {
|
|
181
|
+
const path = join(this.options.logDir, f);
|
|
182
|
+
const stats = statSync(path);
|
|
183
|
+
return {
|
|
184
|
+
name: f,
|
|
185
|
+
path,
|
|
186
|
+
size: stats.size,
|
|
187
|
+
modified: stats.mtime,
|
|
188
|
+
};
|
|
189
|
+
})
|
|
190
|
+
.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
191
|
+
} catch {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Limpa todos os arquivos de log
|
|
198
|
+
*/
|
|
199
|
+
clearAll(): void {
|
|
200
|
+
try {
|
|
201
|
+
const files = this.listLogFiles();
|
|
202
|
+
for (const file of files) {
|
|
203
|
+
unlinkSync(file.path);
|
|
204
|
+
}
|
|
205
|
+
this.currentFile = null;
|
|
206
|
+
this.currentSize = 0;
|
|
207
|
+
} catch {
|
|
208
|
+
// Ignora erros
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Instância global
|
|
214
|
+
let globalFileLogger: FileLogger | null = null;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Obtém instância global do FileLogger
|
|
218
|
+
*/
|
|
219
|
+
export function getFileLogger(options?: Partial<FileLoggerOptions>): FileLogger {
|
|
220
|
+
if (!globalFileLogger) {
|
|
221
|
+
globalFileLogger = new FileLogger(options);
|
|
222
|
+
}
|
|
223
|
+
return globalFileLogger;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Configura o FileLogger global
|
|
228
|
+
*/
|
|
229
|
+
export function configureFileLogger(options: Partial<FileLoggerOptions>): void {
|
|
230
|
+
if (globalFileLogger) {
|
|
231
|
+
globalFileLogger = new FileLogger({ ...globalFileLogger, ...options });
|
|
232
|
+
} else {
|
|
233
|
+
globalFileLogger = new FileLogger(options);
|
|
234
|
+
}
|
|
235
|
+
}
|