openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
185 lines • 6.85 kB
JavaScript
/**
* @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 {
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.
this.
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.
}
setClientName(clientName) {
const lowerName = clientName.toLowerCase();
if (lowerName.includes('claude-desktop')) {
this.
}
else if (lowerName.includes('claude')) {
this.
}
else if (lowerName.includes('gemini')) {
this.
}
else if (clientName === DAEMON_CLIENT_NAME) {
this.
}
else if (lowerName.includes('openclaw')) {
this.
}
else if (lowerName.includes('opencode')) {
this.
}
else if (lowerName.includes('codex')) {
this.
}
else if (lowerName.includes('antigravity')) {
this.
}
else if (lowerName.includes('grok') || lowerName.includes('xai')) {
this.
}
else if (lowerName.includes('copilot')) {
this.
}
else {
this.
}
}
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.
type: WatchdogMessageType.LOG_EVENT,
payload: {
mcp_client: this.
tool_invocation: tool_invocation,
},
});
}
async logServerStart(flagUsage) {
this.
type: WatchdogMessageType.LOG_EVENT,
payload: {
mcp_client: this.
server_start: {
flag_usage: flagUsage,
},
},
});
}
async logDailyActiveIfNeeded() {
try {
const state = await this.
if (this.
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.
type: WatchdogMessageType.LOG_EVENT,
payload: {
mcp_client: this.
daily_active: {
days_since_last_active: daysSince,
},
},
});
state.lastActive = new Date().toISOString();
await this.
}
}
catch (err) {
logger?.('Error in logDailyActiveIfNeeded:', err);
}
}
async logServerError(args) {
this.
type: WatchdogMessageType.LOG_EVENT,
payload: {
mcp_client: this.
server_error: {
tool_name: args.toolName
? stripUnderscoreBeforeNumber(args.toolName)
: '',
error_code: args.errorCode,
},
},
});
}
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