aem-dev-mcp-server
Version:
AEM MCP server providing connectivity tools for Adobe Experience Manager® instances
220 lines • 11 kB
JavaScript
import { BaseOSGiService } from '../utils/base-osgi-service.js';
import { createAemLogsSuccessResult, createAemLogsFailureResult } from '../utils/aem-logs-result.js';
import { TIMEOUTS } from '../constants/timeouts.js';
import { AEM_LOGS_ERROR_CODES, LOG_TYPE_PATHS } from '../types/aem-logs.types.js';
import { paginateLogLines, TokenCountError, estimateTokensForLines } from '../utils/pagination.utils.js';
const DEFAULT_CONFIG = {
timeout: TIMEOUTS.DEFAULT,
logTimeout: (TIMEOUTS.DEFAULT * 2),
actionDelayMs: 500,
maxLogLines: 50000
};
export class AemLogsService extends BaseOSGiService {
#config;
constructor(httpClient, config = {}) {
const fullConfig = { ...DEFAULT_CONFIG, ...config };
super(httpClient, fullConfig);
this.#config = fullConfig;
}
async searchLogs(instance, regex, logType, page) {
const startTime = Date.now();
try {
const regexValidation = this.#validateRegexPattern(regex);
if (!regexValidation.valid) {
return createAemLogsFailureResult(this.#createAemLogsError(AEM_LOGS_ERROR_CODES.INVALID_REGEX_PATTERN, regexValidation.error || 'Invalid regex pattern'), Date.now() - startTime);
}
const logPath = LOG_TYPE_PATHS[logType];
if (!logPath) {
return createAemLogsFailureResult(this.#createAemLogsError(AEM_LOGS_ERROR_CODES.UNSUPPORTED_LOG_TYPE, `Unsupported log type: ${logType}`), Date.now() - startTime);
}
const logsResult = await this.#fetchLogsFromAem(instance, logPath, regex);
if (!logsResult.success) {
return createAemLogsFailureResult(logsResult.error, Date.now() - startTime);
}
const rawLogs = logsResult.data || '';
this.logger.debug('Raw logs received', {
instance: instance.url,
logType,
rawLogsLength: rawLogs.length,
firstChars: rawLogs.substring(0, 100)
});
let allLines = rawLogs
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0);
try {
const regexPattern = new RegExp(regex, 'i');
const filteredLines = allLines.filter(line => regexPattern.test(line));
this.logger.debug('Client-side regex filtering', {
instance: instance.url,
originalLines: allLines.length,
filteredLines: filteredLines.length,
regex
});
allLines = filteredLines;
}
catch (error) {
this.logger.warn('Invalid regex pattern, using all lines', {
instance: instance.url,
regex,
error: error instanceof Error ? error.message : 'Unknown error'
});
}
this.logger.debug('Processed log lines', {
instance: instance.url,
logType,
totalLines: allLines.length,
firstLines: allLines.slice(0, 3)
});
if (allLines.length > this.#config.maxLogLines) {
this.logger.warn(`Processing large log file with ${allLines.length} lines (max recommended: ${this.#config.maxLogLines}). This may impact performance.`, {
instance: instance.url,
logType,
totalLines: allLines.length
});
}
const paginationResult = this.#paginateLogs(allLines, page);
if (!paginationResult.success) {
return createAemLogsFailureResult(paginationResult.error, Date.now() - startTime);
}
this.logger.debug('Pagination applied', {
instance: instance.url,
totalLines: allLines.length,
pageLines: paginationResult.data.entriesOnPage,
currentPage: paginationResult.data.currentPage,
totalPages: paginationResult.data.totalPages,
estimatedTokensForPage: estimateTokensForLines(paginationResult.data.paginatedLines)
});
this.logger.debug('Pagination result', {
instance: instance.url,
logType,
pagination: {
currentPage: paginationResult.data.currentPage,
totalPages: paginationResult.data.totalPages,
totalEntries: paginationResult.data.totalEntries,
entriesOnPage: paginationResult.data.entriesOnPage,
entriesPreview: paginationResult.data.paginatedLines.slice(0, 2)
}
});
const logSearchResult = {
instance: instance.url,
log_type: logType,
entries: paginationResult.data.paginatedLines,
pagination: {
current_page: paginationResult.data.currentPage,
total_pages: paginationResult.data.totalPages,
total_entries: paginationResult.data.totalEntries,
entries_on_page: paginationResult.data.entriesOnPage
}
};
const result = {
success: true,
result: logSearchResult,
message: `Found ${paginationResult.data.totalEntries} log entries matching pattern`
};
this.logger.debug('Created LogOperationResult', {
instance: instance.url,
success: result.success,
message: result.message,
hasResult: !!result.result,
resultInstance: result.result?.instance
});
return createAemLogsSuccessResult(result, Date.now() - startTime);
}
catch (error) {
return createAemLogsFailureResult(this.#createAemLogsError(AEM_LOGS_ERROR_CODES.LOG_PARSING_ERROR, `Failed to search logs: ${error instanceof Error ? error.message : 'Unknown error'}`), Date.now() - startTime);
}
}
async #fetchLogsFromAem(instance, logPath, regex) {
const startTime = Date.now();
try {
const fullUrl = `/system/console/slinglog/tailer.txt?name=${encodeURIComponent(logPath)}&grep=${encodeURIComponent(regex)}&tail=-1`;
this.logger.debug('Making request to AEM log endpoint', {
instance: instance.url,
logPath,
regex,
fullUrl
});
const response = await this.makeAuthenticatedRequest(instance, fullUrl, 'GET', undefined, this.#config.logTimeout, 'Log tailer service unavailable', 'Authentication required for log access');
this.logger.debug('Response from AEM log endpoint', {
instance: instance.url,
success: response.success,
dataType: response.success ? typeof response.data : 'no data',
dataLength: response.success ? (response.data?.length || 0) : 0,
dataPreview: response.success ? response.data?.substring(0, 200) : 'no preview',
errorCode: !response.success ? response.error?.code : undefined,
errorMessage: !response.success ? response.error?.message : undefined,
duration: response.duration
});
if (!response.success) {
let aemLogsError;
if (response.error.message.includes('not found') || response.error.message.includes('404')) {
aemLogsError = this.#createAemLogsError(AEM_LOGS_ERROR_CODES.LOG_FILE_NOT_FOUND, `Log file not found: ${logPath}`);
}
else if (response.error.message.includes('Authentication') || response.error.message.includes('403')) {
aemLogsError = this.#createAemLogsError(AEM_LOGS_ERROR_CODES.LOG_ACCESS_DENIED, 'Access denied to log file');
}
else {
aemLogsError = this.#createAemLogsError(AEM_LOGS_ERROR_CODES.LOG_PARSING_ERROR, response.error.message);
}
return createAemLogsFailureResult(aemLogsError, Date.now() - startTime);
}
return createAemLogsSuccessResult(response.data || '', Date.now() - startTime);
}
catch (error) {
return createAemLogsFailureResult(this.#createAemLogsError(AEM_LOGS_ERROR_CODES.LOG_PARSING_ERROR, `Failed to fetch logs: ${error instanceof Error ? error.message : 'Unknown error'}`), Date.now() - startTime);
}
}
#paginateLogs(lines, page) {
try {
if (page < 1) {
return createAemLogsFailureResult(this.#createAemLogsError(AEM_LOGS_ERROR_CODES.INVALID_PAGE_NUMBER, 'Page number must be at least 1'), 0);
}
const paginationResult = paginateLogLines(lines, page);
if (page > paginationResult.totalPages && paginationResult.totalPages > 0) {
return createAemLogsFailureResult(this.#createAemLogsError(AEM_LOGS_ERROR_CODES.INVALID_PAGE_NUMBER, `Requested page ${page} exceeds total pages ${paginationResult.totalPages}`), 0);
}
return createAemLogsSuccessResult(paginationResult, 0);
}
catch (error) {
let errorCode = AEM_LOGS_ERROR_CODES.PAGINATION_ERROR;
let message = 'Failed to paginate logs';
if (error instanceof TokenCountError) {
errorCode = AEM_LOGS_ERROR_CODES.TOKEN_COUNT_ERROR;
message = `Token counting failed: ${error.message}`;
}
else if (error instanceof Error) {
message = `Pagination failed: ${error.message}`;
}
return createAemLogsFailureResult(this.#createAemLogsError(errorCode, message, error), 0);
}
}
#validateRegexPattern(pattern) {
try {
new RegExp(pattern);
if (pattern.length === 0) {
return { valid: false, error: 'Regex pattern cannot be empty' };
}
if (pattern.length > 1000) {
return { valid: false, error: 'Regex pattern too long (max 1000 characters)' };
}
return { valid: true };
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
return {
valid: false,
error: `Invalid regex pattern '${pattern}': ${errorMsg}. Examples of valid patterns: 'ERROR.*', '\\d{4}-\\d{2}-\\d{2}', 'Exception|Error'`
};
}
}
#createAemLogsError(code, message, details, retry) {
return {
code,
message,
details,
retry: retry ?? false
};
}
}
//# sourceMappingURL=aem-logs.service.js.map