有能监控并提醒 LLM 提供商变动模型列表的工具吗?

看到了这个上个月的帖子 阶跃也加上claude了 ,楼主贴的图片中有监控提供商的模型变动(新增和删除)的通知,感觉挺不错的,现在提供商多了,加到New API里的各个模型列表很难追上更新的进度。

求问一下有知道这是哪一款工具么?或者有类似功能的么?没有的话就只能打算自己糊一款凑合用用了。

好的,我自己糊一个吧 :upside_down_face:

哈哈,感觉自己ai几句话糊一个就好了

随便写一个定期查询+Webhook 不就好了,让 AI 写

糊好了:

/**
 * LLM Models Monitor for Cloudflare Workers
 * 
 * 1. 修改 PROVIDERS 配置;
 * 2. 按需修改 CONFIG 配置;
 * 3. 按需添加提供商 API Key 至环境变量;
 * 4. 添加 Telegram 通知环境变量:
 *    - TELEGRAM_BOT_TOKEN
 *    - TELEGRAM_CHAT_ID
 * 5. 添加 KV 绑定,名称随意,命名空间为 MODEL_MONITOR;
 * 6. 部署到 Cloudflare Workers;
 * 7. 配置 CRON 定时任务(可选);
 * 8. 访问 xxx.workers.dev/monitor 手动触发监控;
 * 9. 访问 xxx.workers.dev/status 查看当前状态;
 * 10. 访问 xxx.workers.dev/clear 清除所有缓存;
 * 11. 访问 xxx.workers.dev/health 健康检查。
 */

// ======================== 配置管理 ========================

/**
 * 提供商配置
 * @typedef {Object} Provider
 * @property {string} id - 提供商唯一标识
 * @property {string} name - 提供商名称
 * @property {string} endpoint - API端点
 * @property {Object} headers - 请求头配置
 * @property {Function} [parseResponse] - 自定义响应解析函数
 */

const PROVIDERS = [
  {
    id: 'openai',
    name: 'OpenAI',
    endpoint: 'https://api.openai.com/v1/models',
    headers: {
      'Authorization': 'Bearer {{OPENAI_API_KEY}}'
    }
  },
  {
    id: 'anthropic',
    name: 'Anthropic',
    endpoint: 'https://api.anthropic.com/v1/models?limit=100',
    headers: {
      'x-api-key': '{{ANTHROPIC_API_KEY}}',
      'anthropic-version': '2023-06-01'
    }
  },
  {
    id: 'deepseek',
    name: 'DeepSeek',
    endpoint: 'https://api.deepseek.com/v1/models',
    headers: {
      'Authorization': 'Bearer {{DEEPSEEK_API_KEY}}'
    }
  },
  {
    id: 'nebius',
    name: 'Nebius',
    endpoint: 'https://api.studio.nebius.com/v1/models',
    headers: {
      'Authorization': 'Bearer {{NEBIUS_API_KEY}}'
    }
  },
  {
    id: 'cerebras',
    name: 'Cerebras',
    endpoint: 'https://api.cerebras.ai/v1/models',
    headers: {
      'Authorization': 'Bearer {{CEREBRAS_API_KEY}}'
    }
  },
  {
    id: 'novita',
    name: 'Novita',
    endpoint: 'https://api.novita.ai/openai/v1/models',
    headers: {
      'Authorization': 'Bearer {{NOVITA_API_KEY}}'
    }
  },
  {
    id: 'mistral',
    name: 'Mistral',
    endpoint: 'https://api.mistral.ai/v1/models',
    headers: {
      'Authorization': 'Bearer {{MISTRAL_API_KEY}}'
    }
  },
  {
    id: 'xai',
    name: 'xAI',
    endpoint: 'https://api.x.ai/v1/models',
    headers: {
      'Authorization': 'Bearer {{XAI_API_KEY}}'
    }
  },
  {
    id: 'groq',
    name: 'Groq',
    endpoint: 'https://api.groq.com/openai/v1/models',
    headers: {
      'Authorization': 'Bearer {{GROQ_API_KEY}}'
    }
  },
  {
    id: 'siliconflow',
    name: '硅基流动',
    endpoint: 'https://api.siliconflow.cn/v1/models',
    headers: {
      'Authorization': 'Bearer {{SILICONFLOW_API_KEY}}'
    }
  },
  {
    id: 'openrouter',
    name: 'OpenRouter',
    endpoint: 'https://openrouter.ai/api/v1/models',
    headers: {
      'Authorization': 'Bearer {{OPENROUTER_API_KEY}}'
    }
  },
  {
    id: 'vercel',
    name: 'Vercel AI Gateway',
    endpoint: 'https://ai-gateway.vercel.sh/v1/models',
    headers: {
      'Authorization': 'Bearer {{VERCEL_API_KEY}}'
    }
  },
  {
    id: 'akash',
    name: 'Akash',
    endpoint: 'https://chatapi.akash.network/api/v1/models',
    headers: {
      'Authorization': 'Bearer {{AKASH_API_KEY}}'
    }
  },
  {
    id: 'v0',
    name: 'v0',
    endpoint: 'https://api.v0.dev/v1/models',
    headers: {
      'Authorization': 'Bearer {{V0_API_KEY}}'
    }
  },
  {
    id: 'bigmodel',
    name: '智谱',
    endpoint: 'https://open.bigmodel.cn/api/paas/v4/models',
    headers: {
      'Authorization': 'Bearer {{BIGMODEL_API_KEY}}'
    }
  },
  {
    id: 'gemini',
    name: 'Gemini',
    endpoint: 'https://generativelanguage.googleapis.com/v1beta/openai/models',
    headers: {
      'Authorization': 'Bearer {{GEMINI_API_KEY}}'
    }
  }
];

// 配置常量
const CONFIG = {
  REQUEST_TIMEOUT: 10000, // 10秒超时
  BATCH_SIZE: 5, // 并发请求批次大小
  BATCH_DELAY: 1000, // 批次间延迟
  MAX_RETRIES: 0, // 最大重试次数
  RETRY_DELAY: 2000, // 重试延迟
};

// ======================== 工具类 ========================

class Logger {
  static log(level, message, data = {}) {
    const timestamp = new Date().toISOString();
    console.log(JSON.stringify({
      timestamp,
      level,
      message,
      ...data
    }));
  }

  static info(message, data) {
    this.log('INFO', message, data);
  }

  static error(message, data) {
    this.log('ERROR', message, data);
  }

  static debug(message, data) {
    this.log('DEBUG', message, data);
  }
}

class EnvironmentHelper {
  /**
   * 替换模板字符串中的环境变量
   */
  static replaceEnvVars(template, env) {
    if (typeof template !== 'string') return template;
    
    return template.replace(/\{\{([^}]+)\}\}/g, (match, envKey) => {
      const value = env[envKey];
      if (!value) {
        Logger.debug(`Environment variable ${envKey} not found`);
      }
      return value || match;
    });
  }

  /**
   * 检查提供商是否已配置
   */
  static isProviderConfigured(provider, env) {
    const requiredEnvVars = this.extractEnvVars(provider.headers);
    return requiredEnvVars.every(key => !!env[key]);
  }

  /**
   * 提取需要的环境变量名
   */
  static extractEnvVars(headers) {
    const vars = [];
    for (const value of Object.values(headers)) {
      if (typeof value === 'string') {
        const matches = value.matchAll(/\{\{([^}]+)\}\}/g);
        for (const match of matches) {
          vars.push(match[1]);
        }
      }
    }
    return vars;
  }
}

// ======================== 核心业务逻辑 ========================

class ModelFetcher {
  constructor(provider, env) {
    this.provider = provider;
    this.env = env;
  }

  /**
   * 获取模型列表,支持重试
   */
  async fetch(retries = CONFIG.MAX_RETRIES) {
    try {
      const headers = this.prepareHeaders();
      const response = await this.makeRequest(headers);
      
      if (!response.ok) {
        throw new Error(await this.formatError(response));
      }
      
      const data = await response.json();
      const models = this.parseModels(data);
      
      return {
        success: true,
        provider: this.provider.id,
        models,
        count: models.length,
        timestamp: new Date().toISOString()
      };
      
    } catch (error) {
      if (retries > 0) {
        Logger.info(`Retrying ${this.provider.name}`, { retriesLeft: retries - 1 });
        await new Promise(resolve => setTimeout(resolve, CONFIG.RETRY_DELAY));
        return this.fetch(retries - 1);
      }
      
      return {
        success: false,
        provider: this.provider.id,
        error: error.message,
        timestamp: new Date().toISOString()
      };
    }
  }

  /**
   * 准备请求头
   */
  prepareHeaders() {
    const headers = {
      'User-Agent': 'Mozilla/5.0 (compatible; LLMModelMonitor/1.0)',
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    };

    for (const [key, value] of Object.entries(this.provider.headers)) {
      headers[key] = EnvironmentHelper.replaceEnvVars(value, this.env);
    }

    return headers;
  }

  /**
   * 发起HTTP请求
   */
  async makeRequest(headers) {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), CONFIG.REQUEST_TIMEOUT);

    try {
      const response = await fetch(this.provider.endpoint, {
        method: 'GET',
        headers,
        signal: controller.signal
      });
      return response;
    } finally {
      clearTimeout(timeout);
    }
  }

  /**
   * 格式化错误信息
   */
  async formatError(response) {
    let errorDetails = `HTTP ${response.status}: ${response.statusText}`;
    try {
      const errorBody = await response.text();
      if (errorBody) {
        errorDetails += ` - ${errorBody.substring(0, 200)}`;
      }
    } catch (e) {
      // 忽略错误体读取失败
    }
    return errorDetails;
  }

  /**
   * 解析模型数据
   */
  parseModels(data) {
    // 默认解析逻辑
    let models = [];
    
    if (data.data && Array.isArray(data.data)) {
      // OpenAI格式
      models = data.data.map(m => ({
        id: m.id,
        name: m.id,
        created: m.created,
        owned_by: m.owned_by
      }));
    } else if (data.models && Array.isArray(data.models)) {
      // Anthropic格式
      models = data.models.map(m => ({
        id: m.model_id || m.id,
        name: m.display_name || m.name || m.model_id || m.id,
        created: m.created_at || m.created,
        owned_by: this.provider.name
      }));
    } else if (Array.isArray(data)) {
      // 直接数组格式
      models = data.map(m => ({
        id: m.id || m.model_id || m.name,
        name: m.name || m.model_name || m.id,
        created: m.created || m.created_at,
        owned_by: m.owned_by || this.provider.name
      }));
    }

    return models;
  }
}

class ModelComparator {
  /**
   * 比较新旧模型列表差异
   */
  static compare(oldData, newData) {
    if (!oldData || !oldData.models) {
      return {
        added: newData.models || [],
        removed: [],
        isFirstTime: true
      };
    }
    
    const oldIds = new Set(oldData.models.map(m => m.id));
    const newIds = new Set(newData.models.map(m => m.id));
    
    const added = newData.models.filter(m => !oldIds.has(m.id));
    const removed = oldData.models.filter(m => !newIds.has(m.id));
    
    return {
      added,
      removed,
      isFirstTime: false,
      hasChanges: added.length > 0 || removed.length > 0
    };
  }
}

class NotificationService {
  constructor(env) {
    this.env = env;
    this.botToken = env.TELEGRAM_BOT_TOKEN;
    this.chatId = env.TELEGRAM_CHAT_ID;
  }

  /**
   * 发送Telegram通知
   */
  async send(message) {
    if (!this.isConfigured()) {
      Logger.info('Telegram not configured, skipping notification');
      return;
    }

    const url = `https://api.telegram.org/bot${this.botToken}/sendMessage`;
    
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          chat_id: this.chatId,
          text: message,
          parse_mode: 'HTML',
          disable_web_page_preview: true
        })
      });

      if (!response.ok) {
        throw new Error(`Telegram API error: ${response.status}`);
      }
    } catch (error) {
      Logger.error('Failed to send Telegram notification', { error: error.message });
    }
  }

  /**
   * 检查是否已配置
   */
  isConfigured() {
    return !!(this.botToken && this.chatId);
  }

  /**
   * 格式化模型变更通知
   */
  formatChangeNotification(provider, changes, newData) {
    const emoji = changes.isFirstTime ? '🆕' : '🔔';
    let message = `${emoji} <b>模型提供商: ${provider.name}</b>\n\n`;
    
    if (changes.isFirstTime) {
      message += `✅ <b>首次监控</b>\n`;
      message += `📊 当前模型数: ${newData.count}\n`;
    } else {
      if (changes.added.length > 0) {
        message += `<b>➕ 新增模型 (${changes.added.length}):</b>\n`;
        changes.added.forEach(model => {
          message += `  • ${model.name || model.id}\n`;
        });
        message += '\n';
      }
      
      if (changes.removed.length > 0) {
        message += `<b>➖ 移除模型 (${changes.removed.length}):</b>\n`;
        changes.removed.forEach(model => {
          message += `  • ${model.name || model.id}\n`;
        });
        message += '\n';
      }
      
      message += `📊 当前模型总数: ${newData.count}\n`;
    }
    
    message += `\n⏰ 更新时间: ${this.formatTime()}`;
    
    return message;
  }

  /**
   * 格式化错误通知
   */
  formatErrorNotification(provider, errorData) {
    let message = `❌ <b>监控失败: ${provider.name}</b>\n\n`;
    message += `⚠️ <b>错误:</b>\n<code>${errorData.error}</code>\n\n`;
    
    const requiredVars = EnvironmentHelper.extractEnvVars(provider.headers);
    if (requiredVars.length > 0) {
      message += `🔑 <b>需要配置:</b>\n`;
      requiredVars.forEach(v => {
        message += `  • ${v}\n`;
      });
    }
    
    message += `\n⏰ 时间: ${this.formatTime()}`;
    
    return message;
  }

  /**
   * 格式化时间
   */
  formatTime() {
    return new Date().toLocaleString('zh-CN', { 
      timeZone: 'Asia/Shanghai',
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit'
    });
  }
}

class StorageService {
  constructor(kv) {
    this.kv = kv;
  }

  /**
   * 获取存储的模型数据
   */
  async getModelData(providerId) {
    const key = `models_${providerId}`;
    const data = await this.kv.get(key);
    return data ? JSON.parse(data) : null;
  }

  /**
   * 保存模型数据
   */
  async saveModelData(providerId, data) {
    const key = `models_${providerId}`;
    await this.kv.put(key, JSON.stringify(data));
  }

  /**
   * 删除模型数据
   */
  async deleteModelData(providerId) {
    const key = `models_${providerId}`;
    await this.kv.delete(key);
  }

  /**
   * 清除所有数据
   */
  async clearAll(providers) {
    const promises = providers.map(p => this.deleteModelData(p.id));
    await Promise.all(promises);
  }
}

// ======================== 监控协调器 ========================

class MonitorOrchestrator {
  constructor(env) {
    this.env = env;
    this.storage = new StorageService(env.MODEL_MONITOR);
    this.notifier = new NotificationService(env);
  }

  /**
   * 执行监控
   */
  async execute() {
    const results = [];
    const configuredProviders = this.getConfiguredProviders();
    
    Logger.info('Starting models monitoring', { 
      providers: configuredProviders.length 
    });

    // 批量并发处理
    const batches = this.createBatches(configuredProviders, CONFIG.BATCH_SIZE);
    
    for (const batch of batches) {
      const batchResults = await this.processBatch(batch);
      results.push(...batchResults);
      
      // 批次间延迟
      if (batches.indexOf(batch) < batches.length - 1) {
        await new Promise(resolve => setTimeout(resolve, CONFIG.BATCH_DELAY));
      }
    }

    Logger.info('Models monitoring completed', { 
      total: results.length,
      successful: results.filter(r => r.success).length 
    });

    return results;
  }

  /**
   * 获取已配置的提供商
   */
  getConfiguredProviders() {
    return PROVIDERS.filter(provider => {
      const isConfigured = EnvironmentHelper.isProviderConfigured(provider, this.env);
      if (!isConfigured) {
        const requiredVars = EnvironmentHelper.extractEnvVars(provider.headers);
        Logger.debug(`Skipping ${provider.name}`, { 
          missingVars: requiredVars 
        });
      }
      return isConfigured;
    });
  }

  /**
   * 创建批次
   */
  createBatches(items, batchSize) {
    const batches = [];
    for (let i = 0; i < items.length; i += batchSize) {
      batches.push(items.slice(i, i + batchSize));
    }
    return batches;
  }

  /**
   * 处理批次
   */
  async processBatch(providers) {
    const promises = providers.map(provider => this.processProvider(provider));
    return Promise.all(promises);
  }

  /**
   * 处理单个提供商
   */
  async processProvider(provider) {
    try {
      Logger.info(`Processing ${provider.name}`);
      
      // 获取新数据
      const fetcher = new ModelFetcher(provider, this.env);
      const newData = await fetcher.fetch();
      
      if (!newData.success) {
        // 发送错误通知
        const errorMessage = this.notifier.formatErrorNotification(provider, newData);
        await this.notifier.send(errorMessage);
        return newData;
      }
      
      // 获取旧数据并比较
      const oldData = await this.storage.getModelData(provider.id);
      const changes = ModelComparator.compare(oldData, newData);
      
      // 如果有变化,发送通知并保存
      if (changes.isFirstTime || changes.hasChanges) {
        const notification = this.notifier.formatChangeNotification(provider, changes, newData);
        await this.notifier.send(notification);
        await this.storage.saveModelData(provider.id, newData);
      }
      
      return {
        ...newData,
        changes
      };
      
    } catch (error) {
      Logger.error(`Error processing ${provider.name}`, { 
        error: error.message 
      });
      
      return {
        success: false,
        provider: provider.id,
        error: error.message,
        timestamp: new Date().toISOString()
      };
    }
  }

  /**
   * 获取当前状态
   */
  async getStatus() {
    const status = {};
    
    for (const provider of PROVIDERS) {
      const data = await this.storage.getModelData(provider.id);
      
      if (data) {
        status[provider.id] = {
          name: provider.name,
          count: data.count,
          lastUpdate: data.timestamp,
          success: data.success,
          configured: EnvironmentHelper.isProviderConfigured(provider, this.env)
        };
      } else {
        status[provider.id] = {
          name: provider.name,
          configured: EnvironmentHelper.isProviderConfigured(provider, this.env),
          status: 'not_monitored'
        };
      }
    }
    
    return status;
  }
}

// ======================== HTTP路由处理 ========================

class Router {
  constructor(env) {
    this.env = env;
    this.orchestrator = new MonitorOrchestrator(env);
    this.storage = new StorageService(env.MODEL_MONITOR);
  }

  /**
   * 处理请求
   */
  async handle(request) {
    const url = new URL(request.url);
    const path = url.pathname;
    const method = request.method;

    // 路由映射
    const routes = {
      'GET /': () => this.handleIndex(),
      'GET /monitor': () => this.handleMonitor(),
      'GET /status': () => this.handleStatus(),
      'POST /clear': () => this.handleClear(),
      'GET /health': () => this.handleHealth(),
    };

    const routeKey = `${method} ${path}`;
    const handler = routes[routeKey];

    if (handler) {
      return handler();
    }

    return this.notFound();
  }

  /**
   * 首页
   */
  handleIndex() {
    const html = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LLM Models Monitor</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 40px 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
        }
        .container {
            background: white;
            border-radius: 12px;
            padding: 32px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
        }
        h1 {
            color: #333;
            margin-bottom: 8px;
        }
        .subtitle {
            color: #666;
            margin-bottom: 32px;
        }
        .endpoints {
            background: #f8f9fa;
            border-radius: 8px;
            padding: 24px;
        }
        .endpoint {
            display: flex;
            align-items: center;
            margin-bottom: 16px;
            padding: 12px;
            background: white;
            border-radius: 6px;
            transition: transform 0.2s;
        }
        .endpoint:hover {
            transform: translateX(4px);
        }
        .method {
            display: inline-block;
            padding: 4px 8px;
            border-radius: 4px;
            font-weight: 600;
            font-size: 12px;
            margin-right: 12px;
            min-width: 50px;
            text-align: center;
        }
        .method.get { background: #e3f2fd; color: #1976d2; }
        .method.post { background: #e8f5e9; color: #388e3c; }
        .path {
            font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
            color: #333;
            font-weight: 500;
            margin-right: 16px;
        }
        .description {
            color: #666;
            font-size: 14px;
        }
        .footer {
            margin-top: 32px;
            padding-top: 24px;
            border-top: 1px solid #e0e0e0;
            text-align: center;
            color: #999;
            font-size: 14px;
        }
        a {
            color: #667eea;
            text-decoration: none;
        }
        a:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🤖 LLM Models Monitor</h1>
        <p class="subtitle">Monitor the model list changes of major LLM providers</p>
        
        <div class="endpoints">
            <h3>Endpoints</h3>
            
            <div class="endpoint">
                <span class="method get">GET</span>
                <span class="path">/monitor</span>
                <span class="description">Manually trigger model monitoring</span>
            </div>
            
            <div class="endpoint">
                <span class="method get">GET</span>
                <span class="path">/status</span>
                <span class="description">View current monitoring status</span>
            </div>
            
            <div class="endpoint">
                <span class="method post">POST</span>
                <span class="path">/clear</span>
                <span class="description">Clear all cached data</span>
            </div>
            
            <div class="endpoint">
                <span class="method get">GET</span>
                <span class="path">/health</span>
                <span class="description">Health check</span>
            </div>
        </div>
    </div>
</body>
</html>
    `;
    
    return new Response(html, {
      headers: { 'Content-Type': 'text/html; charset=utf-8' }
    });
  }

  /**
   * 手动监控
   */
  async handleMonitor() {
    const results = await this.orchestrator.execute();
    
    return this.json({
      success: true,
      timestamp: new Date().toISOString(),
      summary: {
        total: results.length,
        successful: results.filter(r => r.success).length,
        failed: results.filter(r => !r.success).length,
        changed: results.filter(r => r.changes && r.changes.hasChanges).length
      },
      results
    });
  }

  /**
   * 状态查询
   */
  async handleStatus() {
    const status = await this.orchestrator.getStatus();
    
    return this.json({
      success: true,
      timestamp: new Date().toISOString(),
      providers: status
    });
  }

  /**
   * 清除缓存
   */
  async handleClear() {
    await this.storage.clearAll(PROVIDERS);
    
    return this.json({
      success: true,
      message: 'All cached data cleared',
      timestamp: new Date().toISOString()
    });
  }

  /**
   * 健康检查
   */
  handleHealth() {
    return this.json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      version: '2.0.0'
    });
  }

  /**
   * 404响应
   */
  notFound() {
    return this.json({
      error: 'Not Found',
      message: 'The requested endpoint does not exist'
    }, 404);
  }

  /**
   * JSON响应助手
   */
  json(data, status = 200) {
    return new Response(JSON.stringify(data, null, 2), {
      status,
      headers: {
        'Content-Type': 'application/json',
        'Cache-Control': 'no-cache'
      }
    });
  }
}

// ======================== Worker入口 ========================

export default {
  /**
   * HTTP请求处理
   */
  async fetch(request, env, ctx) {
    const router = new Router(env);
    
    try {
      return await router.handle(request);
    } catch (error) {
      Logger.error('Unhandled error', { error: error.message });
      
      return new Response(JSON.stringify({
        error: 'Internal Server Error',
        message: error.message
      }), {
        status: 500,
        headers: { 'Content-Type': 'application/json' }
      });
    }
  },
  
  /**
   * Cron定时任务处理
   */
  async scheduled(event, env, ctx) {
    Logger.info('Scheduled monitoring started', {
      cron: event.cron,
      scheduledTime: event.scheduledTime
    });
    
    try {
      const orchestrator = new MonitorOrchestrator(env);
      await orchestrator.execute();
      Logger.info('Scheduled monitoring completed');
    } catch (error) {
      Logger.error('Scheduled monitoring failed', { 
        error: error.message 
      });
    }
  }
};
1 个赞