158 lines
5.0 KiB
TypeScript
158 lines
5.0 KiB
TypeScript
import memoize from 'lodash-es/memoize.js'
|
|
|
|
export type DebugFilter = {
|
|
include: string[]
|
|
exclude: string[]
|
|
isExclusive: boolean
|
|
}
|
|
|
|
/**
|
|
* Parse debug filter string into a filter configuration
|
|
* Examples:
|
|
* - "api,hooks" -> include only api and hooks categories
|
|
* - "!1p,!file" -> exclude logging and file categories
|
|
* - undefined/empty -> no filtering (show all)
|
|
*/
|
|
export const parseDebugFilter = memoize(
|
|
(filterString?: string): DebugFilter | null => {
|
|
if (!filterString || filterString.trim() === '') {
|
|
return null
|
|
}
|
|
|
|
const filters = filterString
|
|
.split(',')
|
|
.map(f => f.trim())
|
|
.filter(Boolean)
|
|
|
|
// If no valid filters remain, return null
|
|
if (filters.length === 0) {
|
|
return null
|
|
}
|
|
|
|
// Check for mixed inclusive/exclusive filters
|
|
const hasExclusive = filters.some(f => f.startsWith('!'))
|
|
const hasInclusive = filters.some(f => !f.startsWith('!'))
|
|
|
|
if (hasExclusive && hasInclusive) {
|
|
// For now, we'll treat this as an error case and show all messages
|
|
// Log error using logForDebugging to avoid console.error lint rule
|
|
// We'll import and use it later when the circular dependency is resolved
|
|
// For now, just return null silently
|
|
return null
|
|
}
|
|
|
|
// Clean up filters (remove ! prefix) and normalize
|
|
const cleanFilters = filters.map(f => f.replace(/^!/, '').toLowerCase())
|
|
|
|
return {
|
|
include: hasExclusive ? [] : cleanFilters,
|
|
exclude: hasExclusive ? cleanFilters : [],
|
|
isExclusive: hasExclusive,
|
|
}
|
|
},
|
|
)
|
|
|
|
/**
|
|
* Extract debug categories from a message
|
|
* Supports multiple patterns:
|
|
* - "category: message" -> ["category"]
|
|
* - "[CATEGORY] message" -> ["category"]
|
|
* - "MCP server \"name\": message" -> ["mcp", "name"]
|
|
* - "[ANT-ONLY] 1P event: tengu_timer" -> ["ant-only", "1p"]
|
|
*
|
|
* Returns lowercase categories for case-insensitive matching
|
|
*/
|
|
export function extractDebugCategories(message: string): string[] {
|
|
const categories: string[] = []
|
|
|
|
// Pattern 3: MCP server "servername" - Check this first to avoid false positives
|
|
const mcpMatch = message.match(/^MCP server ["']([^"']+)["']/)
|
|
if (mcpMatch && mcpMatch[1]) {
|
|
categories.push('mcp')
|
|
categories.push(mcpMatch[1].toLowerCase())
|
|
} else {
|
|
// Pattern 1: "category: message" (simple prefix) - only if not MCP pattern
|
|
const prefixMatch = message.match(/^([^:[]+):/)
|
|
if (prefixMatch && prefixMatch[1]) {
|
|
categories.push(prefixMatch[1].trim().toLowerCase())
|
|
}
|
|
}
|
|
|
|
// Pattern 2: [CATEGORY] at the start
|
|
const bracketMatch = message.match(/^\[([^\]]+)]/)
|
|
if (bracketMatch && bracketMatch[1]) {
|
|
categories.push(bracketMatch[1].trim().toLowerCase())
|
|
}
|
|
|
|
// Pattern 4: Check for additional categories in the message
|
|
// e.g., "[ANT-ONLY] 1P event: tengu_timer" should match both "ant-only" and "1p"
|
|
if (message.toLowerCase().includes('1p event:')) {
|
|
categories.push('1p')
|
|
}
|
|
|
|
// Pattern 5: Look for secondary categories after the first pattern
|
|
// e.g., "AutoUpdaterWrapper: Installation type: development"
|
|
const secondaryMatch = message.match(
|
|
/:\s*([^:]+?)(?:\s+(?:type|mode|status|event))?:/,
|
|
)
|
|
if (secondaryMatch && secondaryMatch[1]) {
|
|
const secondary = secondaryMatch[1].trim().toLowerCase()
|
|
// Only add if it's a reasonable category name (not too long, no spaces)
|
|
if (secondary.length < 30 && !secondary.includes(' ')) {
|
|
categories.push(secondary)
|
|
}
|
|
}
|
|
|
|
// If no categories found, return empty array (uncategorized)
|
|
return Array.from(new Set(categories)) // Remove duplicates
|
|
}
|
|
|
|
/**
|
|
* Check if debug message should be shown based on filter
|
|
* @param categories - Categories extracted from the message
|
|
* @param filter - Parsed filter configuration
|
|
* @returns true if message should be shown
|
|
*/
|
|
export function shouldShowDebugCategories(
|
|
categories: string[],
|
|
filter: DebugFilter | null,
|
|
): boolean {
|
|
// No filter means show everything
|
|
if (!filter) {
|
|
return true
|
|
}
|
|
|
|
// If no categories found, handle based on filter mode
|
|
if (categories.length === 0) {
|
|
// In exclusive mode, uncategorized messages are excluded by default for security
|
|
// In inclusive mode, uncategorized messages are excluded (must match a category)
|
|
return false
|
|
}
|
|
|
|
if (filter.isExclusive) {
|
|
// Exclusive mode: show if none of the categories are in the exclude list
|
|
return !categories.some(cat => filter.exclude.includes(cat))
|
|
} else {
|
|
// Inclusive mode: show if any of the categories are in the include list
|
|
return categories.some(cat => filter.include.includes(cat))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main function to check if a debug message should be shown
|
|
* Combines extraction and filtering
|
|
*/
|
|
export function shouldShowDebugMessage(
|
|
message: string,
|
|
filter: DebugFilter | null,
|
|
): boolean {
|
|
// Fast path: no filter means show everything
|
|
if (!filter) {
|
|
return true
|
|
}
|
|
|
|
// Only extract categories if we have a filter
|
|
const categories = extractDebugCategories(message)
|
|
return shouldShowDebugCategories(categories, filter)
|
|
}
|