UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

185 lines • 6.85 kB
/** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import process from 'node:process'; import { DAEMON_CLIENT_NAME } from '../daemon/utils.js'; import { logger } from '../utils/logger.js'; import { bucketizeLatency, buildContext, sanitizeParams, stripUnderscoreBeforeNumber, } from './transformation.js'; import { McpClient, WatchdogMessageType, OsType, } from './types.js'; import { WatchdogClient } from './WatchdogClient.js'; const MS_PER_DAY = 24 * 60 * 60 * 1000; function detectOsType() { switch (process.platform) { case 'win32': return OsType.OS_TYPE_WINDOWS; case 'darwin': return OsType.OS_TYPE_MACOS; case 'linux': return OsType.OS_TYPE_LINUX; default: return OsType.OS_TYPE_UNSPECIFIED; } } // Not const to allow resetting the instance for testing purposes. let _clearcut_logger_instance; export class ClearcutLogger { #persistence; #watchdog; #mcpClient; static initialize(options) { if (_clearcut_logger_instance) { throw new Error('ClearcutLogger is already initialized'); } _clearcut_logger_instance = new ClearcutLogger(options); return _clearcut_logger_instance; } static get() { return _clearcut_logger_instance; } static resetForTesting() { _clearcut_logger_instance = undefined; } constructor(options) { this.#persistence = options.persistence; this.#watchdog = options.watchdogClient ?? new WatchdogClient({ parentPid: process.pid, appVersion: options.appVersion, osType: detectOsType(), logFile: options.logFile, clearcutEndpoint: options.clearcutEndpoint, clearcutForceFlushIntervalMs: options.clearcutForceFlushIntervalMs, clearcutIncludePidHeader: options.clearcutIncludePidHeader, }); this.#mcpClient = McpClient.MCP_CLIENT_UNSPECIFIED; } setClientName(clientName) { const lowerName = clientName.toLowerCase(); if (lowerName.includes('claude-desktop')) { this.#mcpClient = McpClient.MCP_CLIENT_CLAUDE_DESKTOP; } else if (lowerName.includes('claude')) { this.#mcpClient = McpClient.MCP_CLIENT_CLAUDE_CODE; } else if (lowerName.includes('gemini')) { this.#mcpClient = McpClient.MCP_CLIENT_GEMINI_CLI; } else if (clientName === DAEMON_CLIENT_NAME) { this.#mcpClient = McpClient.MCP_CLIENT_DT_MCP_CLI; } else if (lowerName.includes('openclaw')) { this.#mcpClient = McpClient.MCP_CLIENT_OPENCLAW; } else if (lowerName.includes('opencode')) { this.#mcpClient = McpClient.MCP_CLIENT_OPENCODE; } else if (lowerName.includes('codex')) { this.#mcpClient = McpClient.MCP_CLIENT_CODEX; } else if (lowerName.includes('antigravity')) { this.#mcpClient = McpClient.MCP_CLIENT_ANTIGRAVITY; } else if (lowerName.includes('grok') || lowerName.includes('xai')) { this.#mcpClient = McpClient.MCP_CLIENT_GROK; } else if (lowerName.includes('copilot')) { this.#mcpClient = McpClient.MCP_CLIENT_GITHUB_COPILOT; } else { this.#mcpClient = McpClient.MCP_CLIENT_OTHER; } } async logToolInvocation(args) { const context = buildContext(args.devToolsData, args.pageUrl); const sanitizedToolName = stripUnderscoreBeforeNumber(args.toolName); const tool_invocation = { tool_name: sanitizedToolName, success: args.success, latency_ms: bucketizeLatency(args.latencyMs), }; if (Object.keys(context).length > 0) { tool_invocation.context = context; } if (Object.keys(args.params).length > 0) { tool_invocation.tool_params = { [`${sanitizedToolName}_params`]: sanitizeParams(args.params, args.schema), }; } this.#watchdog.send({ type: WatchdogMessageType.LOG_EVENT, payload: { mcp_client: this.#mcpClient, tool_invocation: tool_invocation, }, }); } async logServerStart(flagUsage) { this.#watchdog.send({ type: WatchdogMessageType.LOG_EVENT, payload: { mcp_client: this.#mcpClient, server_start: { flag_usage: flagUsage, }, }, }); } async logDailyActiveIfNeeded() { try { const state = await this.#persistence.loadState(); if (this.#shouldLogDailyActive(state)) { let daysSince = -1; if (state.lastActive) { const lastActiveDate = new Date(state.lastActive); const now = new Date(); const diffTime = Math.abs(now.getTime() - lastActiveDate.getTime()); daysSince = Math.ceil(diffTime / MS_PER_DAY); } this.#watchdog.send({ type: WatchdogMessageType.LOG_EVENT, payload: { mcp_client: this.#mcpClient, daily_active: { days_since_last_active: daysSince, }, }, }); state.lastActive = new Date().toISOString(); await this.#persistence.saveState(state); } } catch (err) { logger?.('Error in logDailyActiveIfNeeded:', err); } } async logServerError(args) { this.#watchdog.send({ type: WatchdogMessageType.LOG_EVENT, payload: { mcp_client: this.#mcpClient, server_error: { tool_name: args.toolName ? stripUnderscoreBeforeNumber(args.toolName) : '', error_code: args.errorCode, }, }, }); } #shouldLogDailyActive(state) { if (!state.lastActive) { return true; } const lastActiveDate = new Date(state.lastActive); const now = new Date(); // Compare UTC dates const isSameDay = lastActiveDate.getUTCFullYear() === now.getUTCFullYear() && lastActiveDate.getUTCMonth() === now.getUTCMonth() && lastActiveDate.getUTCDate() === now.getUTCDate(); return !isSameDay; } } //# sourceMappingURL=ClearcutLogger.js.map