@agimon-ai/log-sink-mcp 0.2.13 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +11 -11
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +22 -22
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{stdio-D5FY3NXS.cjs → stdio-D-S2Mhwk.cjs} +17 -17
- package/dist/stdio-D-S2Mhwk.cjs.map +1 -0
- package/dist/stdio-DSDgdLCE.mjs +245 -0
- package/dist/stdio-DSDgdLCE.mjs.map +1 -0
- package/package.json +3 -3
- package/dist/stdio-7vqC2Ouf.mjs +0 -245
- package/dist/stdio-7vqC2Ouf.mjs.map +0 -1
- package/dist/stdio-D5FY3NXS.cjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,c as t,d as n,f as r,i,l as a,n as o,o as s,p as c,r as l,s as u,t as d,u as f}from"./stdio-
|
|
2
|
+
import{a as e,c as t,d as n,f as r,i,l as a,n as o,o as s,p as c,r as l,s as u,t as d,u as f}from"./stdio-DSDgdLCE.mjs";import{readFileSync as p}from"node:fs";import{dirname as m,join as h}from"node:path";import{fileURLToPath as g}from"node:url";import{Command as _,InvalidArgumentError as v}from"commander";import{DEFAULT_PORT_RANGE as y,PortRegistryService as ee}from"@agimon-ai/foundation-port-registry";import{createProcessLease as b,resolveSiblingRegistryPath as x}from"@agimon-ai/foundation-process-registry";import{serve as te}from"@hono/node-server";import*as S from"node:fs/promises";import{Hono as C}from"hono";import{cors as ne}from"hono/cors";import{rateLimiter as re}from"hono-rate-limiter";import{z as w}from"zod";import{raw as T}from"hono/html";import{jsx as E,jsxs as D}from"hono/jsx/jsx-runtime";let O=function(e){return e.TRACE=`trace`,e.DEBUG=`debug`,e.INFO=`info`,e.WARN=`warn`,e.ERROR=`error`,e.FATAL=`fatal`,e}({});const ie=w.object({service:w.string().optional(),level:w.string().optional(),traceId:w.string().optional(),search:w.string().optional(),limit:w.coerce.number().min(1).max(1e3).default(25),offset:w.coerce.number().min(0).default(0)});function ae(e){let t=new C,n=e.get(c.LogQueryService),r=e.get(c.LogSearchService);return t.get(`/logs`,async e=>{try{let{service:t,level:i,traceId:a,search:o,limit:s,offset:c}=ie.parse({service:e.req.query(`service`),level:e.req.query(`level`),traceId:e.req.query(`traceId`),search:e.req.query(`search`),limit:e.req.query(`limit`),offset:e.req.query(`offset`)}),l,u=0;if(o){let e={};t&&(e.service=t),i&&(e.level=i);let n=await r.searchLogsPaginated(o,{...e,offset:c},s);l=n.results,u=n.total}else{let e=i?i.split(`,`):[O.INFO,O.WARN,O.ERROR,O.FATAL],r=await n.queryLogs({level:e,service:t||void 0,traceId:a||void 0,limit:s,offset:c});l=r.logs,u=r.total}return e.json({logs:l,total:u,hasMore:c+s<u})}catch(t){return console.error(`Failed to query logs:`,t),e.json({error:`Failed to query logs`,message:t instanceof Error?t.message:String(t)},500)}}),t.get(`/services`,async e=>{try{let t=await n.getActiveServices();return e.json({services:t,total:t.length})}catch(t){return console.error(`Failed to get services:`,t),e.json({error:`Failed to get services`,message:t instanceof Error?t.message:String(t)},500)}}),t.get(`/stats`,async e=>{try{let t=await n.getStatistics(),r=t.reduce((e,t)=>e+t.count,0),i=t.filter(e=>e.level===`error`||e.level===`fatal`).reduce((e,t)=>e+t.count,0),a=new Set(t.map(e=>e.service)).size;return e.json({totalLogs:r,errors:i,services:a,breakdown:t})}catch(t){return console.error(`Failed to get statistics:`,t),e.json({error:`Failed to get statistics`,message:t instanceof Error?t.message:String(t)},500)}}),t}function oe(e){return e.toLocaleString(`en-US`,{year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1})}const se=`log-table`,k=`level-trace`,A=`level-debug`,j=`level-info`,ce=`level-warn`,le=`level-error`,ue=`level-fatal`,M=`stat-card`,de=`trace-link`,fe=`timestamp-cell`,N=`service-cell`,P=`pagination-btn`,F=`log-entry-group`,I=`metadata-row`,L=`message-row`,R=`message-full-cell`,z=`attributes-cell`;function pe(e){let t=e.toLowerCase();return t===`trace`?k:t===`debug`?A:t===`info`?j:t===`warn`?ce:t===`error`?le:t===`fatal`?ue:``}function me(e){if(!e||typeof e!=`object`)return null;let t=e,n={};for(let[e,r]of Object.entries(t))e===`trace.id`||e===`span.id`||(n[e]=r);return Object.keys(n).length>0?n:null}function he({logs:e}){return E(`div`,{class:`table-container`,children:D(`table`,{class:se,children:[E(`thead`,{children:D(`tr`,{children:[E(`th`,{children:`Timestamp`}),E(`th`,{children:`Level`}),E(`th`,{children:`Service`}),E(`th`,{children:`Trace ID`})]})}),e.map(e=>{let t=me(e.metadata);return D(`tbody`,{class:F,children:[D(`tr`,{class:I,children:[E(`td`,{class:fe,children:oe(e.timestamp)}),E(`td`,{class:pe(e.level),children:e.level.toUpperCase()}),E(`td`,{class:N,children:e.service}),E(`td`,{children:e.traceId?D(`span`,{class:de,onclick:`dashboard.filterByTrace('${e.traceId}')`,children:[e.traceId.slice(0,8),`...`]}):E(`span`,{children:`-`})})]}),E(`tr`,{class:L,children:D(`td`,{colspan:4,class:R,children:[E(`div`,{children:e.message}),t&&E(`pre`,{class:z,children:JSON.stringify(t,null,2)})]})})]},e.id)})]})})}function ge(){return E(`input`,{id:`search-input`,type:`text`,class:`input-field`,placeholder:`Search logs...`,oninput:`dashboard.handleSearchInput(event)`})}function _e({services:e}){return D(`select`,{id:`service-select`,class:`select-field`,onchange:`dashboard.handleServiceChange(event)`,children:[E(`option`,{value:``,children:`All Services`}),e.map(e=>E(`option`,{value:e,children:e},e))]})}function ve({stats:e}){return D(`div`,{class:`stats-container`,children:[D(`div`,{class:M,children:[E(`h3`,{children:`Total Logs`}),E(`div`,{class:`value`,children:e.totalLogs.toLocaleString()})]}),D(`div`,{class:M,children:[E(`h3`,{children:`Errors`}),E(`div`,{class:`value`,children:e.errors.toLocaleString()})]}),D(`div`,{class:M,children:[E(`h3`,{children:`Services`}),E(`div`,{class:`value`,children:e.services})]})]})}function ye({initialLogs:e,services:t,stats:n}){return D(`div`,{class:`dashboard-container`,children:[D(`div`,{class:`dashboard-header`,children:[E(`h1`,{children:`Log Dashboard`}),E(`p`,{children:`Real-time log streaming with auto-refresh (3 seconds)`})]}),E(ve,{stats:n}),D(`div`,{class:`controls-section`,children:[E(ge,{}),E(_e,{services:t}),E(`button`,{id:`refresh-btn`,class:`btn`,onclick:`dashboard.manualRefresh()`,children:`Refresh Now`})]}),E(he,{logs:e}),D(`div`,{id:`pagination-controls`,class:`pagination-container`,children:[E(`button`,{id:`prev-btn`,class:P,onclick:`dashboard.prevPage()`,disabled:!0,children:`Previous`}),E(`span`,{id:`page-info`,class:`page-info`,children:`Page 1`}),E(`button`,{id:`next-btn`,class:P,onclick:`dashboard.nextPage()`,children:`Next`})]}),E(`script`,{children:T(`
|
|
3
3
|
class DashboardManager {
|
|
4
4
|
constructor() {
|
|
5
5
|
this.filters = {
|
|
@@ -69,7 +69,7 @@ class DashboardManager {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
updateLogTable(logs) {
|
|
72
|
-
const table = document.querySelector('.${
|
|
72
|
+
const table = document.querySelector('.${se}');
|
|
73
73
|
if (!table) return;
|
|
74
74
|
|
|
75
75
|
// Remove existing tbody elements (except thead)
|
|
@@ -81,26 +81,26 @@ class DashboardManager {
|
|
|
81
81
|
const levelClass = this.getLevelClass(log.level);
|
|
82
82
|
const timestamp = this.formatTimestamp(log.timestamp);
|
|
83
83
|
const traceIdHtml = log.traceId
|
|
84
|
-
? \`<span class="${
|
|
84
|
+
? \`<span class="${de}" onclick="dashboard.filterByTrace('\${log.traceId}')">\${log.traceId.slice(0, 8)}...</span>\`
|
|
85
85
|
: '<span>-</span>';
|
|
86
86
|
|
|
87
87
|
// Get display attributes (filter out trace/span IDs)
|
|
88
88
|
const displayAttrs = this.getDisplayAttributes(log.metadata);
|
|
89
89
|
const attrsHtml = displayAttrs
|
|
90
|
-
? \`<pre class="${
|
|
90
|
+
? \`<pre class="${z}">\${this.escapeHtml(JSON.stringify(displayAttrs, null, 2))}</pre>\`
|
|
91
91
|
: '';
|
|
92
92
|
|
|
93
93
|
const tbody = document.createElement('tbody');
|
|
94
|
-
tbody.className = '${
|
|
94
|
+
tbody.className = '${F}';
|
|
95
95
|
tbody.innerHTML = \`
|
|
96
|
-
<tr class="${
|
|
97
|
-
<td class="${
|
|
96
|
+
<tr class="${I}">
|
|
97
|
+
<td class="${fe}">\${timestamp}</td>
|
|
98
98
|
<td class="\${levelClass}">\${log.level.toUpperCase()}</td>
|
|
99
|
-
<td class="${
|
|
99
|
+
<td class="${N}">\${log.service}</td>
|
|
100
100
|
<td>\${traceIdHtml}</td>
|
|
101
101
|
</tr>
|
|
102
|
-
<tr class="${
|
|
103
|
-
<td colspan="4" class="${
|
|
102
|
+
<tr class="${L}">
|
|
103
|
+
<td colspan="4" class="${R}">
|
|
104
104
|
<div>\${this.escapeHtml(log.message)}</div>
|
|
105
105
|
\${attrsHtml}
|
|
106
106
|
</td>
|
|
@@ -133,12 +133,12 @@ class DashboardManager {
|
|
|
133
133
|
|
|
134
134
|
getLevelClass(level) {
|
|
135
135
|
const classes = {
|
|
136
|
-
trace: '${
|
|
137
|
-
debug: '${
|
|
138
|
-
info: '${
|
|
139
|
-
warn: '${
|
|
140
|
-
error: '${
|
|
141
|
-
fatal: '${
|
|
136
|
+
trace: '${k}',
|
|
137
|
+
debug: '${A}',
|
|
138
|
+
info: '${j}',
|
|
139
|
+
warn: '${ce}',
|
|
140
|
+
error: '${le}',
|
|
141
|
+
fatal: '${ue}',
|
|
142
142
|
};
|
|
143
143
|
return classes[level.toLowerCase()] || '';
|
|
144
144
|
}
|
|
@@ -241,7 +241,7 @@ const dashboard = new DashboardManager();
|
|
|
241
241
|
window.addEventListener('beforeunload', () => {
|
|
242
242
|
dashboard.stopAutoRefresh();
|
|
243
243
|
});
|
|
244
|
-
`)})]})}function
|
|
244
|
+
`)})]})}function B({title:e,children:t}){return D(`html`,{lang:`en`,children:[D(`head`,{children:[E(`meta`,{charset:`UTF-8`}),E(`meta`,{name:`viewport`,content:`width=device-width, initial-scale=1.0`}),E(`title`,{children:e}),E(`style`,{children:T(`
|
|
245
245
|
* {
|
|
246
246
|
margin: 0;
|
|
247
247
|
padding: 0;
|
|
@@ -556,10 +556,10 @@ window.addEventListener('beforeunload', () => {
|
|
|
556
556
|
color: #555;
|
|
557
557
|
margin-top: 0.25rem;
|
|
558
558
|
}
|
|
559
|
-
`)})]}),D(`body`,{children:t})]})}function ye(e){let t=new C,n=e.get(c.LogQueryService);return t.get(`/`,async e=>{try{let t=await n.getActiveServices(),r=await n.getStatistics(),i=await n.filterByLevel([k.INFO,k.WARN,k.ERROR,k.FATAL],100),a={totalLogs:r.reduce((e,t)=>e+t.count,0),errors:r.filter(e=>e.level===`error`||e.level===`fatal`).reduce((e,t)=>e+t.count,0),services:new Set(r.map(e=>e.service)).size};return e.html(D(V,{title:`Log Dashboard`,children:D(ve,{initialLogs:i,services:t,stats:a})}))}catch(t){return console.error(`Failed to render dashboard:`,t),e.html(D(V,{title:`Log Dashboard - Error`,children:O(`div`,{style:`padding: 2rem; text-align: center;`,children:[D(`h1`,{children:`Failed to load dashboard`}),D(`p`,{children:t instanceof Error?t.message:String(t)}),D(`a`,{href:`/`,style:`color: #2196f3; text-decoration: underline;`,children:`Retry`})]})}),500)}}),t}const H=5*1024*1024,U=2e3,be=8e3,xe=T.object({timestamp:T.string().datetime().optional(),level:T.enum([`trace`,`debug`,`info`,`warn`,`error`,`fatal`]),message:T.string().min(1).max(be),traceId:T.string().regex(/^[0-9a-f]{32}$/i,`traceId must be 32 hex characters`).optional(),spanId:T.string().regex(/^[0-9a-f]{16}$/i,`spanId must be 16 hex characters`).optional(),parentSpanId:T.string().regex(/^[0-9a-f]{16}$/i,`parentSpanId must be 16 hex characters`).optional(),service:T.string().min(1).max(200),hostname:T.string().max(255).optional(),pid:T.number().int().nonnegative().optional(),metadata:T.record(T.string(),T.unknown()).optional(),errorType:T.string().max(255).optional(),errorMessage:T.string().max(8e3).optional(),errorStack:T.string().max(32e3).optional()}),Se=T.object({logs:T.array(xe).min(1).max(1e3)});function W(e){if(!e||!/^\d+$/.test(e))throw Error(`OTLP timestamp must be a numeric unix-nanoseconds string`);let t=Number(BigInt(e)/BigInt(1e6));return new Date(t)}function G(e,t){return typeof e==`string`&&RegExp(`^[0-9a-f]{${t}}$`,`i`).test(e)}function K(e){if(!e)return null;let t=Number(e);return!Number.isFinite(t)||t<=0?null:t>H?`Request body exceeds ${H} bytes`:null}async function q(e){try{return await e.req.json()}catch(e){throw Error(`Invalid JSON body: ${e instanceof Error?e.message:String(e)}`)}}function Ce(e){if(!Array.isArray(e.resourceSpans)||e.resourceSpans.length===0)throw Error(`Invalid OTLP trace request: missing resourceSpans`);let t=0;for(let n of e.resourceSpans){if(!Array.isArray(n.scopeSpans))throw Error(`Invalid OTLP trace request: scopeSpans must be an array`);for(let e of n.scopeSpans){if(!Array.isArray(e.spans))throw Error(`Invalid OTLP trace request: spans must be an array`);for(let n of e.spans){if(t+=1,t>U)throw Error(`OTLP trace request exceeds ${U} spans`);if(!G(n.traceId,32))throw Error(`Invalid OTLP trace request: traceId must be 32 hex characters`);if(!G(n.spanId,16))throw Error(`Invalid OTLP trace request: spanId must be 16 hex characters`);if(n.parentSpanId&&!G(n.parentSpanId,16))throw Error(`Invalid OTLP trace request: parentSpanId must be 16 hex characters`);if(!n.name||n.name.length>be)throw Error(`Invalid OTLP trace request: span name is missing or too long`);W(n.startTimeUnixNano)}}}}function we(e){if(!Array.isArray(e.resourceLogs)||e.resourceLogs.length===0)throw Error(`Invalid OTLP logs request: missing resourceLogs`);let t=0;for(let n of e.resourceLogs){if(!Array.isArray(n.scopeLogs))throw Error(`Invalid OTLP logs request: scopeLogs must be an array`);for(let e of n.scopeLogs){if(!Array.isArray(e.logRecords))throw Error(`Invalid OTLP logs request: logRecords must be an array`);for(let n of e.logRecords){if(t+=1,t>U)throw Error(`OTLP logs request exceeds ${U} log records`);if(n.traceId&&!G(n.traceId,32))throw Error(`Invalid OTLP logs request: traceId must be 32 hex characters`);if(n.spanId&&!G(n.spanId,16))throw Error(`Invalid OTLP logs request: spanId must be 16 hex characters`);if(n.severityText&&n.severityText.length>32)throw Error(`Invalid OTLP logs request: severityText is too long`);W(n.timeUnixNano)}}}}function Te(e){return(e.resource?.attributes?.find(e=>e.key===`service.name`))?.value?.stringValue||`unknown-service`}function J(e,t){let n=e?.find(e=>e.key===t);if(!n)return;let{value:r}=n;if(r.stringValue!==void 0)return r.stringValue;if(r.intValue!==void 0)return r.intValue;if(r.doubleValue!==void 0)return String(r.doubleValue);if(r.boolValue!==void 0)return String(r.boolValue)}function Ee(e){return(e.resource?.attributes?.find(e=>e.key===`service.name`))?.value?.stringValue||`unknown-service`}function De(e){return e===void 0?`info`:e<=4?`trace`:e<=8?`debug`:e<=12?`info`:e<=16?`warn`:e<=20?`error`:`fatal`}function Oe(e,t){let n=W(e.timeUnixNano),r=e.severityText?.toLowerCase()||De(e.severityNumber),i=``;if(e.body?.stringValue)i=e.body.stringValue;else if(e.body?.kvlistValue){let t={};for(let n of e.body.kvlistValue.values){let e=n.value.stringValue??n.value.intValue??n.value.doubleValue??n.value.boolValue;t[n.key]=e}i=JSON.stringify(t)}let a={},o=null,s=null,c=null;if(e.attributes)for(let t of e.attributes){let e=t.value.stringValue??t.value.intValue??t.value.doubleValue??t.value.boolValue;t.key===`exception.type`?o=String(e):t.key===`exception.message`?s=String(e):t.key===`exception.stacktrace`?c=String(e):a[t.key]=e}return{timestamp:n,level:r,message:i||`[LOG] ${t}`,traceId:e.traceId??null,spanId:e.spanId??null,parentSpanId:null,service:t,hostname:null,pid:null,metadata:Object.keys(a).length>0?a:null,errorType:o,errorMessage:s,errorStack:c}}function ke(e,t){let n=W(e.startTimeUnixNano),r=e.status?.code,i=`info`;r===2?i=`error`:r===1&&(i=`info`);let a=J(e.attributes,`exception.type`),o=J(e.attributes,`exception.message`),s=J(e.attributes,`exception.stacktrace`),c={};if(e.attributes)for(let t of e.attributes){let e=t.value.stringValue??t.value.intValue??t.value.doubleValue??t.value.boolValue;c[t.key]=e}return c.spanName=e.name,c.spanKind=e.kind,e.status?.message&&(c.statusMessage=e.status.message),{timestamp:n,level:i,message:`[SPAN] ${e.name}`,traceId:e.traceId,spanId:e.spanId,parentSpanId:e.parentSpanId??null,service:t,hostname:null,pid:null,metadata:c,errorType:a??null,errorMessage:o??null,errorStack:s??null}}function Ae(e){let t=new C,n=e.get(c.LogStorageService);t.use(`*`,w({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`,`Authorization`]})),t.get(`/health`,async e=>{try{return e.json({status:`healthy`,service:`log-sink-mcp-http`,serviceName:`log-sink-mcp-http`,timestamp:new Date().toISOString()})}catch(t){return e.json({status:`unhealthy`,error:t instanceof Error?t.message:String(t)},503)}});let r=ne({windowMs:60*1e3,limit:100,standardHeaders:`draft-6`,keyGenerator:e=>e.req.header(`x-forwarded-for`)||e.req.header(`x-real-ip`)||`unknown`});t.post(`/logs`,r,async e=>{try{let t=K(e.req.header(`content-length`));if(t)return e.json({success:!1,error:t},413);let r=await q(e),i=Se.safeParse(r);if(!i.success)return e.json({success:!1,error:`Validation failed`,details:i.error.issues},400);let{logs:a}=i.data,o=a.map(e=>({...e,timestamp:e.timestamp?new Date(e.timestamp):new Date})),s=await n.insertBatch(o);return e.json({success:!0,count:s.length,message:`Successfully inserted ${s.length} logs`},201)}catch(t){let n=t instanceof Error?t.message:String(t);return n.startsWith(`Invalid JSON body`)?e.json({success:!1,error:n},400):(console.error(`Failed to insert logs:`,t),e.json({success:!1,error:`Failed to insert logs`},500))}}),t.post(`/v1/traces`,r,async e=>{try{let t=K(e.req.header(`content-length`));if(t)return e.json({success:!1,error:t},413);let r=await q(e);Ce(r);let i=[];for(let e of r.resourceSpans){let t=Te(e);for(let n of e.scopeSpans)for(let e of n.spans){let n=ke(e,t);i.push(n)}}let a=await n.insertBatch(i);return e.json({success:!0,count:a.length,message:`Successfully inserted ${a.length} trace spans as logs`},201)}catch(t){let n=t instanceof Error?t.message:String(t);return n.startsWith(`Invalid JSON body`)||n.startsWith(`Invalid OTLP`)||n.startsWith(`OTLP`)?e.json({success:!1,error:n},400):(console.error(`Failed to insert OTLP traces:`,t),e.json({success:!1,error:`Failed to insert OTLP traces`},500))}}),t.post(`/v1/logs`,r,async e=>{try{let t=K(e.req.header(`content-length`));if(t)return e.json({success:!1,error:t},413);let r=await q(e);we(r);let i=[];for(let e of r.resourceLogs){let t=Ee(e);for(let n of e.scopeLogs)for(let e of n.logRecords){let n=Oe(e,t);i.push(n)}}let a=await n.insertBatch(i);return e.json({success:!0,count:a.length,message:`Successfully inserted ${a.length} OTLP logs`},201)}catch(t){let n=t instanceof Error?t.message:String(t);return n.startsWith(`Invalid JSON body`)||n.startsWith(`Invalid OTLP`)||n.startsWith(`OTLP`)?e.json({success:!1,error:n},400):(console.error(`Failed to insert OTLP logs:`,t),e.json({success:!1,error:`Failed to insert OTLP logs`},500))}});let i=ie(e);t.route(`/api`,i);let a=ye(e);return t.route(`/`,a),t}function Y(e,t){if(e===void 0||e===``)return t;let n=Number.parseInt(e,10);if(!Number.isFinite(n)||n<=0)throw Error(`Invalid database retention value: ${e}`);return n}function X(e={}){let t=Y(e.dbMaxBytes??process.env.LOG_SINK_DB_MAX_BYTES,268435456),n=Y(e.dbTargetBytes??process.env.LOG_SINK_DB_TARGET_BYTES,Math.floor(t*.9));return{maxBytes:t,targetBytes:Math.min(n,t),intervalMs:Y(e.dbRetentionIntervalMs??process.env.LOG_SINK_DB_RETENTION_INTERVAL_MS,3e5),batchSize:Y(e.dbRetentionBatchSize??process.env.LOG_SINK_DB_RETENTION_BATCH_SIZE,1e3)}}const je=String(b.min),Me=n();function Ne(e,t){if(!e)return;let n=m.resolve(e);return m.extname(n)===`.json`?m.join(m.dirname(n),t):m.join(n,t)}function Pe(){return new x(process.env.PROCESS_REGISTRY_PATH)}async function Fe(e,t,n,r,i){let a=Pe(),o=await a.registerProcess({repositoryPath:e,serviceName:t,serviceType:`tool`,environment:n,pid:process.pid,port:r,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2),metadata:{dbPath:i,transport:`http`}});if(!o.success||!o.record)throw Error(o.error||`Failed to register process for ${t}`);let s=!1;return{release:async r=>{if(s)return;s=!0;let i=await a.releaseProcess({repositoryPath:e,serviceName:t,serviceType:`tool`,pid:process.pid,environment:n,kill:r?.kill??!1,releasePort:r?.releasePort??!1});if(!i.success&&i.error&&!i.error.includes(`No matching process entry`))throw Error(i.error||`Failed to release process for ${t}`)}}}const Ie=new v(`http-serve`).description(`Start HTTP server for log ingestion with configurable port, database path, and in-memory mode`).option(`-p, --port <port>`,`Port to listen on`,je).option(`--db-path <path>`,`Path to SQLite database file`,Me).option(`--in-memory`,`Use in-memory database (for testing)`,!1).option(`--db-max-bytes <bytes>`,`Maximum SQLite database size before trimming`).option(`--db-target-bytes <bytes>`,`Target SQLite database size after trimming`).option(`--db-retention-interval-ms <ms>`,`How often to check database size`,`300000`).option(`--db-retention-batch-size <count>`,`Number of oldest log rows to delete per trim batch`,`1000`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).action(async e=>{try{let t=Number.parseInt(e.port,10),n={min:3e3,max:Math.max(4999,t)},i=process.env.NODE_ENV||`development`,a=r(process.cwd()),o=`log-sink-mcp-http`;e.registryPath&&(process.env.PORT_REGISTRY_PATH=e.registryPath,process.env.PROCESS_REGISTRY_PATH=Ne(e.registryPath,`processes.json`)??process.env.PROCESS_REGISTRY_PATH);let s=new ee(process.env.PORT_REGISTRY_PATH);console.log(`🚀 Starting HTTP server for log ingestion...`),console.log(` Port: ${t}`),console.log(` Database: ${e.inMemory?`In-Memory`:e.dbPath}`),console.log(` Registry path: ${process.env.PORT_REGISTRY_PATH??`${process.env.HOME}/.port-registry/ports.json`}`);let l=f(),u=l.get(c.LogStorageService),d=l.get(c.LogRetentionService);await u.initializeDatabase(e.dbPath,e.inMemory);let p=d.startSizeRetentionMonitor(X({dbMaxBytes:e.dbMaxBytes,dbTargetBytes:e.dbTargetBytes,dbRetentionIntervalMs:e.dbRetentionIntervalMs,dbRetentionBatchSize:e.dbRetentionBatchSize}));console.log(`✓ Database initialized`);let m=Ae(l),h=await s.reservePort({repositoryPath:a,serviceName:o,serviceType:`tool`,environment:i,preferredPort:t,pid:process.pid,host:`127.0.0.1`,force:!0,portRange:n,metadata:{healthCheckUrl:`http://localhost:${t}/health`,dbPath:e.dbPath}});if(!h.success||!h.record)throw Error(h.error||`Failed to reserve service in global registry`);let g=h.record.port;if(g!==t){let t=await s.reservePort({repositoryPath:a,serviceName:o,serviceType:`tool`,environment:i,preferredPort:g,pid:process.pid,host:`127.0.0.1`,force:!0,portRange:n,metadata:{healthCheckUrl:`http://localhost:${g}/health`,dbPath:e.dbPath}});if(!t.success||!t.record)throw Error(t.error||`Failed to update service metadata in registry`);h=t}if(!h.record)throw Error(`Port reservation lost before startup`);let _=h.record,v=_.port,y=await Fe(a,o,i,v,e.dbPath),b=`http://localhost:${v}/health`,x;try{x=te({fetch:m.fetch,port:v})}catch(e){try{await s.releasePort({repositoryPath:a,serviceName:o,serviceType:`tool`,environment:i,pid:process.pid})}catch{}try{await y.release({kill:!1,releasePort:!1})}catch{}throw e}console.log(`✓ HTTP server listening on http://localhost:${v}`),console.log(` Health check: ${b}`),console.log(` Log ingestion: POST http://localhost:${v}/logs`);let S=_.port,C=_.serviceName||o;console.log(`✓ Service registered: ${C} on ${S}`),console.log(`
|
|
560
|
-
Press Ctrl+C to stop the server`);let w=async e=>{console.log(`\n\n${e} received. Shutting down gracefully...`);try{
|
|
561
|
-
`)}function Q(e){return e.option(`--db-path <path>`,`Path to SQLite database file`,Le).option(`--in-memory`,`Use in-memory database`,!1)}async function We(e,t){let n=f(),r=n.get(c.LogStorageService);await r.initializeDatabase(e.dbPath,e.inMemory);try{return await t(n)}finally{await r.disconnect()}}async function Ge(e,t,n,r=Be){try{let i=await We(e,async e=>t(e).execute(n)),a=Ue(i);return i.isError?(r.stderr(a),1):(r.stdout(a),0)}catch(e){return r.stderr(`Error executing logs command: ${e instanceof Error?e.message:String(e)}`),1}}async function $(e,t,n){let r=await Ge(e,t,n);r!==0&&process.exit(r)}function Ke(e,t){if(!(!e&&!t)){if(!e||!t)throw new y(`Provide both --start-time and --end-time together.`);return{start:e,end:t}}}function qe(){let n=new v(`logs`).description(`Run log analysis commands that mirror MCP tool capabilities`),r=Q(new v(`query`).alias(`query-logs`).description(`Query and filter log entries by level, trace ID, service, or time range`).option(`--level <level...>`,`Log level(s) to filter by`).option(`--trace-id <traceId>`,`Trace ID to filter by`).option(`--service <service...>`,`Service name(s) to filter by`).option(`--start-time <iso8601>`,`Start time for log range (ISO 8601)`).option(`--end-time <iso8601>`,`End time for log range (ISO 8601)`).option(`--limit <number>`,`Maximum number of log entries to return`,Z).action(async e=>{await $(e,e=>new i(e),{level:He(e.level),traceId:e.traceId,service:e.service,startTime:e.startTime,endTime:e.endTime,limit:e.limit})})),o=Q(new v(`search`).alias(`search-logs`).description(`Search log entries with full-text search and optional filters`).argument(`<search-query>`,`FTS5 search query`).option(`--mode <mode>`,`Search strategy: fts, semantic, or hybrid`,`hybrid`).option(`--service <service...>`,`Service name(s) to filter by`).option(`--level <level...>`,`Log level(s) to filter by`).option(`--start-time <iso8601>`,`Start time for log range (ISO 8601)`).option(`--end-time <iso8601>`,`End time for log range (ISO 8601)`).option(`--limit <number>`,`Maximum number of search results to return`,Z).action(async(e,t)=>{await $(t,e=>new l(e),{searchQuery:e,mode:t.mode,service:t.service,level:He(t.level),startTime:t.startTime,endTime:t.endTime,limit:t.limit})})),c=Q(new v(`trace`).alias(`get-trace-timeline`).description(`Get the complete trace timeline for a trace ID`).argument(`<trace-id>`,`Trace ID to inspect`).action(async(t,n)=>{await $(n,t=>new e(t),{traceId:t})})),d=Q(new v(`analyze-errors`).alias(`errors`).description(`Analyze error patterns and group similar errors`).option(`--trace-id <traceId>`,`Optional trace ID to scope the analysis`).option(`--start-time <iso8601>`,`Start time for error analysis (ISO 8601)`).option(`--end-time <iso8601>`,`End time for error analysis (ISO 8601)`).option(`--limit <number>`,`Maximum number of error entries to analyze`,Z).action(async e=>{await $(e,e=>new a(e),{traceId:e.traceId,timeRange:Ke(e.startTime,e.endTime),limit:e.limit})})),f=Q(new v(`stats`).alias(`get-log-stats`).description(`Get aggregated log statistics grouped by level, service, or both`).option(`--start-time <iso8601>`,`Start time for statistics (ISO 8601)`).option(`--end-time <iso8601>`,`End time for statistics (ISO 8601)`).option(`--group-by <groupBy>`,`Group statistics by level, service, or both`,Ve).action(async e=>{await $(e,e=>new u(e),{startTime:e.startTime,endTime:e.endTime,groupBy:e.groupBy})})),p=Q(new v(`services`).alias(`get-services`).description(`Get the list of unique services present in the log database`).action(async e=>{await $(e,e=>new s(e),{})})),m=Q(new v(`clear`).alias(`clear-logs`).description(`Clear all logs from the database`).action(async e=>{await $(e,e=>new t(e),{})}));return n.addCommand(r).addCommand(o).addCommand(c).addCommand(d).addCommand(f).addCommand(p).addCommand(m)}const Je=qe();function Ye(e,t){if(!e)return;let n=m.resolve(e);return m.extname(n)===`.json`?m.join(m.dirname(n),t):m.join(n,t)}function Xe(){return new x(process.env.PROCESS_REGISTRY_PATH)}async function Ze(e){let t=Xe(),n=await t.registerProcess({repositoryPath:process.cwd(),serviceName:e,serviceType:`tool`,environment:process.env.NODE_ENV||`development`,pid:process.pid,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2),metadata:{transport:`stdio`,command:`mcp-serve`},force:!0});if(!n.success||!n.record)throw Error(n.error||`Failed to register process for ${e}`);let r=!1;return{release:async()=>{if(r)return;r=!0;let n=await t.releaseProcess({repositoryPath:process.cwd(),serviceName:e,serviceType:`tool`,environment:process.env.NODE_ENV||`development`,pid:process.pid,kill:!1,releasePort:!1});if(!n.success&&!n.error?.includes(`No matching process entry`))throw Error(n.error||`Failed to release process for ${e}`)}}}function Qe(){return n()}const $e=new v(`mcp-serve`).description(`Start MCP server with stdio transport`).option(`--cleanup`,`Stop HTTP server on MCP server shutdown`,!1).option(`--db-path <path>`,`Path to SQLite database file`,Qe()).option(`--in-memory`,`Use in-memory database (for testing)`,!1).option(`--db-max-bytes <bytes>`,`Maximum SQLite database size before trimming`).option(`--db-target-bytes <bytes>`,`Target SQLite database size after trimming`).option(`--db-retention-interval-ms <ms>`,`How often to check database size`,`300000`).option(`--db-retention-batch-size <count>`,`Number of oldest log rows to delete per trim batch`,`1000`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--registry-dir <path>`,`Custom registry directory for service discovery`).action(async e=>{let t;try{let n=e.registryPath||e.registryDir;n&&(process.env.PORT_REGISTRY_PATH=n,process.env.PROCESS_REGISTRY_PATH=Ye(n,`processes.json`)??process.env.PROCESS_REGISTRY_PATH);let r=f(),i=r.get(c.LogStorageService),a=r.get(c.LogRetentionService),s=r.get(c.HttpServerManager);await i.initializeDatabase(e.dbPath,e.inMemory);let l=a.startSizeRetentionMonitor(X({dbMaxBytes:e.dbMaxBytes,dbTargetBytes:e.dbTargetBytes,dbRetentionIntervalMs:e.dbRetentionIntervalMs,dbRetentionBatchSize:e.dbRetentionBatchSize})),u=await s.ensureRunning(3100,e.dbPath);u.running||console.error(`Warning: HTTP server failed to start: ${u.error}`);let p=new d(o(r));t=await Ze(`log-sink-mcp-mcp-serve`);let m=async n=>{console.error(`\nReceived ${n}, shutting down gracefully...`);try{await p.stop(),e.cleanup&&u.running&&(await s.stop(),console.error(`HTTP server stopped`)),l(),await i.disconnect(),await t?.release(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>m(`SIGINT`)),process.on(`SIGTERM`,()=>m(`SIGTERM`)),await p.start()}catch(e){await t?.release().catch(()=>void 0),console.error(`Failed to start MCP server:`,e),process.exit(1)}});function et(e,t){if(!e)return;let n=m.resolve(e);return m.extname(n)===`.json`?m.join(m.dirname(n),t):m.join(n,t)}function tt(){return new x(process.env.PROCESS_REGISTRY_PATH)}async function nt(e){let t=tt(),n=await t.registerProcess({repositoryPath:process.cwd(),serviceName:e,serviceType:`tool`,environment:process.env.NODE_ENV||`development`,pid:process.pid,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2),metadata:{transport:`stdio`,command:`start`},force:!0});if(!n.success||!n.record)throw Error(n.error||`Failed to register process for ${e}`);let r=!1;return{release:async()=>{if(r)return;r=!0;let n=await t.releaseProcess({repositoryPath:process.cwd(),serviceName:e,serviceType:`tool`,environment:process.env.NODE_ENV||`development`,pid:process.pid,kill:!1,releasePort:!1});if(!n.success&&!n.error?.includes(`No matching process entry`))throw Error(n.error||`Failed to release process for ${e}`)}}}const rt=new v(`start`).description(`Start HTTP and/or MCP servers with singleton coordination`).option(`--mcp-only`,`Start only the MCP server (stdio transport)`,!1).option(`--http-only`,`Start only the HTTP server`,!1).option(`-p, --port <port>`,`Port for HTTP server`,String(b.min)).option(`--db-path <path>`,`Path to SQLite database file`,n()).option(`--in-memory`,`Use in-memory database (for testing)`,!1).option(`--db-max-bytes <bytes>`,`Maximum SQLite database size before trimming`).option(`--db-target-bytes <bytes>`,`Target SQLite database size after trimming`).option(`--db-retention-interval-ms <ms>`,`How often to check database size`,`300000`).option(`--db-retention-batch-size <count>`,`Number of oldest log rows to delete per trim batch`,`1000`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--registry-dir <path>`,`Custom registry directory for service discovery`).action(async e=>{let t;try{let n=e.registryPath||e.registryDir;n&&(process.env.PORT_REGISTRY_PATH=n,process.env.PROCESS_REGISTRY_PATH=et(n,`processes.json`)??process.env.PROCESS_REGISTRY_PATH),e.dbMaxBytes&&(process.env.LOG_SINK_DB_MAX_BYTES=e.dbMaxBytes),e.dbTargetBytes&&(process.env.LOG_SINK_DB_TARGET_BYTES=e.dbTargetBytes),e.dbRetentionIntervalMs&&(process.env.LOG_SINK_DB_RETENTION_INTERVAL_MS=e.dbRetentionIntervalMs),e.dbRetentionBatchSize&&(process.env.LOG_SINK_DB_RETENTION_BATCH_SIZE=e.dbRetentionBatchSize);let r=f(),i=r.get(c.LogStorageService);await i.initializeDatabase(e.dbPath,e.inMemory);let a=Number.parseInt(e.port,10),s=!e.mcpOnly,l=!e.httpOnly,u=r.get(c.LogRetentionService),p=l?u.startSizeRetentionMonitor(X({dbMaxBytes:e.dbMaxBytes,dbTargetBytes:e.dbTargetBytes,dbRetentionIntervalMs:e.dbRetentionIntervalMs,dbRetentionBatchSize:e.dbRetentionBatchSize})):()=>void 0;if(console.log(`🚀 Starting log-sink-mcp services...`),console.log(` Database: ${e.inMemory?`In-Memory`:e.dbPath}`),s){let t=await r.get(c.HttpServerManager).ensureRunning(a,e.dbPath);t.running?(console.log(`✓ HTTP Server: Running on http://localhost:${t.port} (PID: ${t.pid})`),console.log(` Health check: http://localhost:${t.port}/health`),console.log(` Log ingestion: POST http://localhost:${t.port}/logs`)):(console.error(`✗ HTTP Server failed to start: ${t.error}`),l||process.exit(1))}if(l){t=await nt(`log-sink-mcp-start`),console.log(`✓ MCP Server: Starting with stdio transport...`);let e=new d(o(r));await e.start(),console.log(`✓ MCP Server: Ready for connections`);let n=async n=>{console.log(`\n\n${n} received. Shutting down gracefully...`);try{await e.stop(),console.log(`✓ MCP server stopped`),s&&(await r.get(c.HttpServerManager).stop(),console.log(`✓ HTTP server stopped`)),p(),await i.disconnect(),console.log(`✓ Database disconnected`),await t?.release(),console.log(`✓ Process registry entry released`),console.log(`Goodbye! 👋`),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>n(`SIGINT`)),process.on(`SIGTERM`,()=>n(`SIGTERM`)),console.log(`
|
|
559
|
+
`)})]}),E(`body`,{children:t})]})}function be(e){let t=new C,n=e.get(c.LogQueryService);return t.get(`/`,async e=>{try{let t=await n.getActiveServices(),r=await n.getStatistics(),i=await n.filterByLevel([O.INFO,O.WARN,O.ERROR,O.FATAL],100),a={totalLogs:r.reduce((e,t)=>e+t.count,0),errors:r.filter(e=>e.level===`error`||e.level===`fatal`).reduce((e,t)=>e+t.count,0),services:new Set(r.map(e=>e.service)).size};return e.html(E(B,{title:`Log Dashboard`,children:E(ye,{initialLogs:i,services:t,stats:a})}))}catch(t){return console.error(`Failed to render dashboard:`,t),e.html(E(B,{title:`Log Dashboard - Error`,children:D(`div`,{style:`padding: 2rem; text-align: center;`,children:[E(`h1`,{children:`Failed to load dashboard`}),E(`p`,{children:t instanceof Error?t.message:String(t)}),E(`a`,{href:`/`,style:`color: #2196f3; text-decoration: underline;`,children:`Retry`})]})}),500)}}),t}const V=5*1024*1024,H=2e3,U=8e3,xe=w.object({timestamp:w.string().datetime().optional(),level:w.enum([`trace`,`debug`,`info`,`warn`,`error`,`fatal`]),message:w.string().min(1).max(U),traceId:w.string().regex(/^[0-9a-f]{32}$/i,`traceId must be 32 hex characters`).optional(),spanId:w.string().regex(/^[0-9a-f]{16}$/i,`spanId must be 16 hex characters`).optional(),parentSpanId:w.string().regex(/^[0-9a-f]{16}$/i,`parentSpanId must be 16 hex characters`).optional(),service:w.string().min(1).max(200),hostname:w.string().max(255).optional(),pid:w.number().int().nonnegative().optional(),metadata:w.record(w.string(),w.unknown()).optional(),errorType:w.string().max(255).optional(),errorMessage:w.string().max(8e3).optional(),errorStack:w.string().max(32e3).optional()}),Se=w.object({logs:w.array(xe).min(1).max(1e3)});function W(e){if(!e||!/^\d+$/.test(e))throw Error(`OTLP timestamp must be a numeric unix-nanoseconds string`);let t=Number(BigInt(e)/BigInt(1e6));return new Date(t)}function G(e,t){return typeof e==`string`&&RegExp(`^[0-9a-f]{${t}}$`,`i`).test(e)}function K(e){if(!e)return null;let t=Number(e);return!Number.isFinite(t)||t<=0?null:t>V?`Request body exceeds ${V} bytes`:null}async function q(e){try{return await e.req.json()}catch(e){throw Error(`Invalid JSON body: ${e instanceof Error?e.message:String(e)}`)}}function Ce(e){if(!Array.isArray(e.resourceSpans)||e.resourceSpans.length===0)throw Error(`Invalid OTLP trace request: missing resourceSpans`);let t=0;for(let n of e.resourceSpans){if(!Array.isArray(n.scopeSpans))throw Error(`Invalid OTLP trace request: scopeSpans must be an array`);for(let e of n.scopeSpans){if(!Array.isArray(e.spans))throw Error(`Invalid OTLP trace request: spans must be an array`);for(let n of e.spans){if(t+=1,t>H)throw Error(`OTLP trace request exceeds ${H} spans`);if(!G(n.traceId,32))throw Error(`Invalid OTLP trace request: traceId must be 32 hex characters`);if(!G(n.spanId,16))throw Error(`Invalid OTLP trace request: spanId must be 16 hex characters`);if(n.parentSpanId&&!G(n.parentSpanId,16))throw Error(`Invalid OTLP trace request: parentSpanId must be 16 hex characters`);if(!n.name||n.name.length>U)throw Error(`Invalid OTLP trace request: span name is missing or too long`);W(n.startTimeUnixNano)}}}}function we(e){if(!Array.isArray(e.resourceLogs)||e.resourceLogs.length===0)throw Error(`Invalid OTLP logs request: missing resourceLogs`);let t=0;for(let n of e.resourceLogs){if(!Array.isArray(n.scopeLogs))throw Error(`Invalid OTLP logs request: scopeLogs must be an array`);for(let e of n.scopeLogs){if(!Array.isArray(e.logRecords))throw Error(`Invalid OTLP logs request: logRecords must be an array`);for(let n of e.logRecords){if(t+=1,t>H)throw Error(`OTLP logs request exceeds ${H} log records`);if(n.traceId&&!G(n.traceId,32))throw Error(`Invalid OTLP logs request: traceId must be 32 hex characters`);if(n.spanId&&!G(n.spanId,16))throw Error(`Invalid OTLP logs request: spanId must be 16 hex characters`);if(n.severityText&&n.severityText.length>32)throw Error(`Invalid OTLP logs request: severityText is too long`);W(n.timeUnixNano)}}}}function Te(e){return(e.resource?.attributes?.find(e=>e.key===`service.name`))?.value?.stringValue||`unknown-service`}function J(e,t){let n=e?.find(e=>e.key===t);if(!n)return;let{value:r}=n;if(r.stringValue!==void 0)return r.stringValue;if(r.intValue!==void 0)return r.intValue;if(r.doubleValue!==void 0)return String(r.doubleValue);if(r.boolValue!==void 0)return String(r.boolValue)}function Ee(e){return(e.resource?.attributes?.find(e=>e.key===`service.name`))?.value?.stringValue||`unknown-service`}function De(e){return e===void 0?`info`:e<=4?`trace`:e<=8?`debug`:e<=12?`info`:e<=16?`warn`:e<=20?`error`:`fatal`}function Oe(e,t){let n=W(e.timeUnixNano),r=e.severityText?.toLowerCase()||De(e.severityNumber),i=``;if(e.body?.stringValue)i=e.body.stringValue;else if(e.body?.kvlistValue){let t={};for(let n of e.body.kvlistValue.values){let e=n.value.stringValue??n.value.intValue??n.value.doubleValue??n.value.boolValue;t[n.key]=e}i=JSON.stringify(t)}let a={},o=null,s=null,c=null;if(e.attributes)for(let t of e.attributes){let e=t.value.stringValue??t.value.intValue??t.value.doubleValue??t.value.boolValue;t.key===`exception.type`?o=String(e):t.key===`exception.message`?s=String(e):t.key===`exception.stacktrace`?c=String(e):a[t.key]=e}return{timestamp:n,level:r,message:i||`[LOG] ${t}`,traceId:e.traceId??null,spanId:e.spanId??null,parentSpanId:null,service:t,hostname:null,pid:null,metadata:Object.keys(a).length>0?a:null,errorType:o,errorMessage:s,errorStack:c}}function ke(e,t){let n=W(e.startTimeUnixNano),r=e.status?.code,i=`info`;r===2?i=`error`:r===1&&(i=`info`);let a=J(e.attributes,`exception.type`),o=J(e.attributes,`exception.message`),s=J(e.attributes,`exception.stacktrace`),c={};if(e.attributes)for(let t of e.attributes){let e=t.value.stringValue??t.value.intValue??t.value.doubleValue??t.value.boolValue;c[t.key]=e}return c.spanName=e.name,c.spanKind=e.kind,e.status?.message&&(c.statusMessage=e.status.message),{timestamp:n,level:i,message:`[SPAN] ${e.name}`,traceId:e.traceId,spanId:e.spanId,parentSpanId:e.parentSpanId??null,service:t,hostname:null,pid:null,metadata:c,errorType:a??null,errorMessage:o??null,errorStack:s??null}}function Ae(e){let t=new C,n=e.get(c.LogStorageService);t.use(`*`,ne({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`,`Authorization`]})),t.get(`/health`,async e=>{try{return e.json({status:`healthy`,service:`log-sink-mcp-http`,serviceName:`log-sink-mcp-http`,timestamp:new Date().toISOString()})}catch(t){return e.json({status:`unhealthy`,error:t instanceof Error?t.message:String(t)},503)}});let r=re({windowMs:60*1e3,limit:100,standardHeaders:`draft-6`,keyGenerator:e=>e.req.header(`x-forwarded-for`)||e.req.header(`x-real-ip`)||`unknown`});t.post(`/logs`,r,async e=>{try{let t=K(e.req.header(`content-length`));if(t)return e.json({success:!1,error:t},413);let r=await q(e),i=Se.safeParse(r);if(!i.success)return e.json({success:!1,error:`Validation failed`,details:i.error.issues},400);let{logs:a}=i.data,o=a.map(e=>({...e,timestamp:e.timestamp?new Date(e.timestamp):new Date})),s=await n.insertBatch(o);return e.json({success:!0,count:s.length,message:`Successfully inserted ${s.length} logs`},201)}catch(t){let n=t instanceof Error?t.message:String(t);return n.startsWith(`Invalid JSON body`)?e.json({success:!1,error:n},400):(console.error(`Failed to insert logs:`,t),e.json({success:!1,error:`Failed to insert logs`},500))}}),t.post(`/v1/traces`,r,async e=>{try{let t=K(e.req.header(`content-length`));if(t)return e.json({success:!1,error:t},413);let r=await q(e);Ce(r);let i=[];for(let e of r.resourceSpans){let t=Te(e);for(let n of e.scopeSpans)for(let e of n.spans){let n=ke(e,t);i.push(n)}}let a=await n.insertBatch(i);return e.json({success:!0,count:a.length,message:`Successfully inserted ${a.length} trace spans as logs`},201)}catch(t){let n=t instanceof Error?t.message:String(t);return n.startsWith(`Invalid JSON body`)||n.startsWith(`Invalid OTLP`)||n.startsWith(`OTLP`)?e.json({success:!1,error:n},400):(console.error(`Failed to insert OTLP traces:`,t),e.json({success:!1,error:`Failed to insert OTLP traces`},500))}}),t.post(`/v1/logs`,r,async e=>{try{let t=K(e.req.header(`content-length`));if(t)return e.json({success:!1,error:t},413);let r=await q(e);we(r);let i=[];for(let e of r.resourceLogs){let t=Ee(e);for(let n of e.scopeLogs)for(let e of n.logRecords){let n=Oe(e,t);i.push(n)}}let a=await n.insertBatch(i);return e.json({success:!0,count:a.length,message:`Successfully inserted ${a.length} OTLP logs`},201)}catch(t){let n=t instanceof Error?t.message:String(t);return n.startsWith(`Invalid JSON body`)||n.startsWith(`Invalid OTLP`)||n.startsWith(`OTLP`)?e.json({success:!1,error:n},400):(console.error(`Failed to insert OTLP logs:`,t),e.json({success:!1,error:`Failed to insert OTLP logs`},500))}});let i=ae(e);t.route(`/api`,i);let a=be(e);return t.route(`/`,a),t}function Y(e,t){if(e===void 0||e===``)return t;let n=Number.parseInt(e,10);if(!Number.isFinite(n)||n<=0)throw Error(`Invalid database retention value: ${e}`);return n}function X(e={}){let t=Y(e.dbMaxBytes??process.env.LOG_SINK_DB_MAX_BYTES,268435456),n=Y(e.dbTargetBytes??process.env.LOG_SINK_DB_TARGET_BYTES,Math.floor(t*.9));return{maxBytes:t,targetBytes:Math.min(n,t),intervalMs:Y(e.dbRetentionIntervalMs??process.env.LOG_SINK_DB_RETENTION_INTERVAL_MS,3e5),batchSize:Y(e.dbRetentionBatchSize??process.env.LOG_SINK_DB_RETENTION_BATCH_SIZE,1e3)}}const je=String(y.min),Me=n(),Ne=new _(`http-serve`).description(`Start HTTP server for log ingestion with configurable port, database path, and in-memory mode`).option(`-p, --port <port>`,`Port to listen on`,je).option(`--db-path <path>`,`Path to SQLite database file`,Me).option(`--in-memory`,`Use in-memory database (for testing)`,!1).option(`--db-max-bytes <bytes>`,`Maximum SQLite database size before trimming`).option(`--db-target-bytes <bytes>`,`Target SQLite database size after trimming`).option(`--db-retention-interval-ms <ms>`,`How often to check database size`,`300000`).option(`--db-retention-batch-size <count>`,`Number of oldest log rows to delete per trim batch`,`1000`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).action(async e=>{try{let t=Number.parseInt(e.port,10),n={min:3e3,max:Math.max(4999,t)},i=process.env.NODE_ENV||`development`,a=r(process.cwd()),o=`log-sink-mcp-http`;e.registryPath&&(process.env.PORT_REGISTRY_PATH=e.registryPath,process.env.PROCESS_REGISTRY_PATH=x(e.registryPath,`processes.json`)??process.env.PROCESS_REGISTRY_PATH);let s=new ee(process.env.PORT_REGISTRY_PATH);console.log(`🚀 Starting HTTP server for log ingestion...`),console.log(` Port: ${t}`),console.log(` Database: ${e.inMemory?`In-Memory`:e.dbPath}`),console.log(` Registry path: ${process.env.PORT_REGISTRY_PATH??`${process.env.HOME}/.port-registry/ports.json`}`);let l=f(),u=l.get(c.LogStorageService),d=l.get(c.LogRetentionService);await u.initializeDatabase(e.dbPath,e.inMemory);let p=d.startSizeRetentionMonitor(X({dbMaxBytes:e.dbMaxBytes,dbTargetBytes:e.dbTargetBytes,dbRetentionIntervalMs:e.dbRetentionIntervalMs,dbRetentionBatchSize:e.dbRetentionBatchSize}));console.log(`✓ Database initialized`);let m=Ae(l),h=await s.reservePort({repositoryPath:a,serviceName:o,serviceType:`tool`,environment:i,preferredPort:t,pid:process.pid,host:`127.0.0.1`,force:!0,portRange:n,metadata:{healthCheckUrl:`http://localhost:${t}/health`,dbPath:e.dbPath}});if(!h.success||!h.record)throw Error(h.error||`Failed to reserve service in global registry`);let g=h.record.port;if(g!==t){let t=await s.reservePort({repositoryPath:a,serviceName:o,serviceType:`tool`,environment:i,preferredPort:g,pid:process.pid,host:`127.0.0.1`,force:!0,portRange:n,metadata:{healthCheckUrl:`http://localhost:${g}/health`,dbPath:e.dbPath}});if(!t.success||!t.record)throw Error(t.error||`Failed to update service metadata in registry`);h=t}if(!h.record)throw Error(`Port reservation lost before startup`);let _=h.record,v=_.port,y=await b({repositoryPath:a,serviceName:o,serviceType:`tool`,environment:i,pid:process.pid,port:v,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2),metadata:{dbPath:e.dbPath,transport:`http`}}),S=`http://localhost:${v}/health`,C;try{C=te({fetch:m.fetch,port:v})}catch(e){try{await y.release({kill:!1})}catch{}throw e}console.log(`✓ HTTP server listening on http://localhost:${v}`),console.log(` Health check: ${S}`),console.log(` Log ingestion: POST http://localhost:${v}/logs`);let ne=_.port,re=_.serviceName||o;console.log(`✓ Service registered: ${re} on ${ne}`),console.log(`
|
|
560
|
+
Press Ctrl+C to stop the server`);let w=async e=>{console.log(`\n\n${e} received. Shutting down gracefully...`);try{C.close(),console.log(`✓ Server closed`);try{await y.release({kill:!1}),console.log(`✓ Process deregistered`)}catch{}p(),await u.disconnect(),console.log(`✓ Database disconnected`),console.log(`Goodbye! 👋`),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>w(`SIGINT`)),process.on(`SIGTERM`,()=>w(`SIGTERM`))}catch(e){console.error(`Error starting HTTP server:`,e),process.exit(1)}}),Pe=n(),Fe=new Set(Object.values(O)),Ie=new Set([`level`,`service`,`both`]),Le={stdout:e=>console.log(e),stderr:e=>console.error(e)};function Z(e){let t=Number.parseInt(e,10);if(!Number.isInteger(t)||t<=0)throw new v(`Expected a positive integer.`);return t}function Re(e){if(!Ie.has(e))throw new v(`Expected one of: 'level', 'service', 'both'.`);return e}function ze(e){if(!(!e||e.length===0))return e.map(e=>{if(!Fe.has(e))throw new v(`Invalid log level '${e}'. Expected one of: ${Array.from(Fe).join(`, `)}`);return e})}function Be(e){return e.content.map(e=>e.type===`text`?e.text:JSON.stringify(e)).join(`
|
|
561
|
+
`)}function Q(e){return e.option(`--db-path <path>`,`Path to SQLite database file`,Pe).option(`--in-memory`,`Use in-memory database`,!1)}async function Ve(e,t){let n=f(),r=n.get(c.LogStorageService);await r.initializeDatabase(e.dbPath,e.inMemory);try{return await t(n)}finally{await r.disconnect()}}async function He(e,t,n,r=Le){try{let i=await Ve(e,async e=>t(e).execute(n)),a=Be(i);return i.isError?(r.stderr(a),1):(r.stdout(a),0)}catch(e){return r.stderr(`Error executing logs command: ${e instanceof Error?e.message:String(e)}`),1}}async function $(e,t,n){let r=await He(e,t,n);r!==0&&process.exit(r)}function Ue(e,t){if(!(!e&&!t)){if(!e||!t)throw new v(`Provide both --start-time and --end-time together.`);return{start:e,end:t}}}function We(){let n=new _(`logs`).description(`Run log analysis commands that mirror MCP tool capabilities`),r=Q(new _(`query`).alias(`query-logs`).description(`Query and filter log entries by level, trace ID, service, or time range`).option(`--level <level...>`,`Log level(s) to filter by`).option(`--trace-id <traceId>`,`Trace ID to filter by`).option(`--service <service...>`,`Service name(s) to filter by`).option(`--start-time <iso8601>`,`Start time for log range (ISO 8601)`).option(`--end-time <iso8601>`,`End time for log range (ISO 8601)`).option(`--limit <number>`,`Maximum number of log entries to return`,Z).action(async e=>{await $(e,e=>new i(e),{level:ze(e.level),traceId:e.traceId,service:e.service,startTime:e.startTime,endTime:e.endTime,limit:e.limit})})),o=Q(new _(`search`).alias(`search-logs`).description(`Search log entries with full-text search and optional filters`).argument(`<search-query>`,`FTS5 search query`).option(`--mode <mode>`,`Search strategy: fts, semantic, or hybrid`,`hybrid`).option(`--service <service...>`,`Service name(s) to filter by`).option(`--level <level...>`,`Log level(s) to filter by`).option(`--start-time <iso8601>`,`Start time for log range (ISO 8601)`).option(`--end-time <iso8601>`,`End time for log range (ISO 8601)`).option(`--limit <number>`,`Maximum number of search results to return`,Z).action(async(e,t)=>{await $(t,e=>new l(e),{searchQuery:e,mode:t.mode,service:t.service,level:ze(t.level),startTime:t.startTime,endTime:t.endTime,limit:t.limit})})),c=Q(new _(`trace`).alias(`get-trace-timeline`).description(`Get the complete trace timeline for a trace ID`).argument(`<trace-id>`,`Trace ID to inspect`).action(async(t,n)=>{await $(n,t=>new e(t),{traceId:t})})),d=Q(new _(`analyze-errors`).alias(`errors`).description(`Analyze error patterns and group similar errors`).option(`--trace-id <traceId>`,`Optional trace ID to scope the analysis`).option(`--start-time <iso8601>`,`Start time for error analysis (ISO 8601)`).option(`--end-time <iso8601>`,`End time for error analysis (ISO 8601)`).option(`--limit <number>`,`Maximum number of error entries to analyze`,Z).action(async e=>{await $(e,e=>new a(e),{traceId:e.traceId,timeRange:Ue(e.startTime,e.endTime),limit:e.limit})})),f=Q(new _(`stats`).alias(`get-log-stats`).description(`Get aggregated log statistics grouped by level, service, or both`).option(`--start-time <iso8601>`,`Start time for statistics (ISO 8601)`).option(`--end-time <iso8601>`,`End time for statistics (ISO 8601)`).option(`--group-by <groupBy>`,`Group statistics by level, service, or both`,Re).action(async e=>{await $(e,e=>new u(e),{startTime:e.startTime,endTime:e.endTime,groupBy:e.groupBy})})),p=Q(new _(`services`).alias(`get-services`).description(`Get the list of unique services present in the log database`).action(async e=>{await $(e,e=>new s(e),{})})),m=Q(new _(`clear`).alias(`clear-logs`).description(`Clear all logs from the database`).action(async e=>{await $(e,e=>new t(e),{})}));return n.addCommand(r).addCommand(o).addCommand(c).addCommand(d).addCommand(f).addCommand(p).addCommand(m)}const Ge=We();function Ke(){return n()}const qe=new _(`mcp-serve`).description(`Start MCP server with stdio transport`).option(`--cleanup`,`Stop HTTP server on MCP server shutdown`,!1).option(`--db-path <path>`,`Path to SQLite database file`,Ke()).option(`--in-memory`,`Use in-memory database (for testing)`,!1).option(`--db-max-bytes <bytes>`,`Maximum SQLite database size before trimming`).option(`--db-target-bytes <bytes>`,`Target SQLite database size after trimming`).option(`--db-retention-interval-ms <ms>`,`How often to check database size`,`300000`).option(`--db-retention-batch-size <count>`,`Number of oldest log rows to delete per trim batch`,`1000`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--registry-dir <path>`,`Custom registry directory for service discovery`).action(async e=>{let t;try{let n=e.registryPath||e.registryDir;n&&(process.env.PORT_REGISTRY_PATH=n,process.env.PROCESS_REGISTRY_PATH=x(n,`processes.json`)??process.env.PROCESS_REGISTRY_PATH);let r=f(),i=r.get(c.LogStorageService),a=r.get(c.LogRetentionService),s=r.get(c.HttpServerManager);await i.initializeDatabase(e.dbPath,e.inMemory);let l=a.startSizeRetentionMonitor(X({dbMaxBytes:e.dbMaxBytes,dbTargetBytes:e.dbTargetBytes,dbRetentionIntervalMs:e.dbRetentionIntervalMs,dbRetentionBatchSize:e.dbRetentionBatchSize})),u=await s.ensureRunning(3100,e.dbPath);u.running||console.error(`Warning: HTTP server failed to start: ${u.error}`);let p=new d(o(r));t=await b({repositoryPath:process.cwd(),serviceName:`log-sink-mcp-mcp-serve`,serviceType:`tool`,environment:process.env.NODE_ENV||`development`,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2),metadata:{transport:`stdio`,command:`mcp-serve`}});let m=async n=>{console.error(`\nReceived ${n}, shutting down gracefully...`);try{await p.stop(),e.cleanup&&u.running&&(await s.stop(),console.error(`HTTP server stopped`)),l(),await i.disconnect(),await t?.release(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>m(`SIGINT`)),process.on(`SIGTERM`,()=>m(`SIGTERM`)),await p.start()}catch(e){await t?.release().catch(()=>void 0),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),Je=new _(`start`).description(`Start HTTP and/or MCP servers with singleton coordination`).option(`--mcp-only`,`Start only the MCP server (stdio transport)`,!1).option(`--http-only`,`Start only the HTTP server`,!1).option(`-p, --port <port>`,`Port for HTTP server`,String(y.min)).option(`--db-path <path>`,`Path to SQLite database file`,n()).option(`--in-memory`,`Use in-memory database (for testing)`,!1).option(`--db-max-bytes <bytes>`,`Maximum SQLite database size before trimming`).option(`--db-target-bytes <bytes>`,`Target SQLite database size after trimming`).option(`--db-retention-interval-ms <ms>`,`How often to check database size`,`300000`).option(`--db-retention-batch-size <count>`,`Number of oldest log rows to delete per trim batch`,`1000`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--registry-dir <path>`,`Custom registry directory for service discovery`).action(async e=>{let t;try{let n=e.registryPath||e.registryDir;n&&(process.env.PORT_REGISTRY_PATH=n,process.env.PROCESS_REGISTRY_PATH=x(n,`processes.json`)??process.env.PROCESS_REGISTRY_PATH),e.dbMaxBytes&&(process.env.LOG_SINK_DB_MAX_BYTES=e.dbMaxBytes),e.dbTargetBytes&&(process.env.LOG_SINK_DB_TARGET_BYTES=e.dbTargetBytes),e.dbRetentionIntervalMs&&(process.env.LOG_SINK_DB_RETENTION_INTERVAL_MS=e.dbRetentionIntervalMs),e.dbRetentionBatchSize&&(process.env.LOG_SINK_DB_RETENTION_BATCH_SIZE=e.dbRetentionBatchSize);let r=f(),i=r.get(c.LogStorageService);await i.initializeDatabase(e.dbPath,e.inMemory);let a=Number.parseInt(e.port,10),s=!e.mcpOnly,l=!e.httpOnly,u=r.get(c.LogRetentionService),p=l?u.startSizeRetentionMonitor(X({dbMaxBytes:e.dbMaxBytes,dbTargetBytes:e.dbTargetBytes,dbRetentionIntervalMs:e.dbRetentionIntervalMs,dbRetentionBatchSize:e.dbRetentionBatchSize})):()=>void 0;if(console.log(`🚀 Starting log-sink-mcp services...`),console.log(` Database: ${e.inMemory?`In-Memory`:e.dbPath}`),s){let t=await r.get(c.HttpServerManager).ensureRunning(a,e.dbPath);t.running?(console.log(`✓ HTTP Server: Running on http://localhost:${t.port} (PID: ${t.pid})`),console.log(` Health check: http://localhost:${t.port}/health`),console.log(` Log ingestion: POST http://localhost:${t.port}/logs`)):(console.error(`✗ HTTP Server failed to start: ${t.error}`),l||process.exit(1))}if(l){t=await b({repositoryPath:process.cwd(),serviceName:`log-sink-mcp-start`,serviceType:`tool`,environment:process.env.NODE_ENV||`development`,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2),metadata:{transport:`stdio`,command:`start`}}),console.log(`✓ MCP Server: Starting with stdio transport...`);let e=new d(o(r));await e.start(),console.log(`✓ MCP Server: Ready for connections`);let n=async n=>{console.log(`\n\n${n} received. Shutting down gracefully...`);try{await e.stop(),console.log(`✓ MCP server stopped`),s&&(await r.get(c.HttpServerManager).stop(),console.log(`✓ HTTP server stopped`)),p(),await i.disconnect(),console.log(`✓ Database disconnected`),await t?.release(),console.log(`✓ Process registry entry released`),console.log(`Goodbye! 👋`),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>n(`SIGINT`)),process.on(`SIGTERM`,()=>n(`SIGTERM`)),console.log(`
|
|
562
562
|
Press Ctrl+C to stop the server`)}else console.log(`
|
|
563
|
-
✓ HTTP server started in background`),console.log(`Use "log-sink-mcp stop" to stop the HTTP server`),t&&await t.release().catch(()=>void 0),process.exit(0)}catch(e){t&&await t.release().catch(()=>void 0),console.error(`Error starting services:`,e),process.exit(1)}}),
|
|
564
|
-
`),console.log(`─`.repeat(50));let e=await f().get(c.HttpServerManager).getStatus();e.running?(console.log(`HTTP Server: 🟢 Running`),console.log(` PID: ${e.pid}`),console.log(` Port: ${e.port}`),console.log(` Health: http://localhost:${e.port}/health`)):(console.log(`HTTP Server: 🔴 Not Running`),e.error&&console.log(` Error: ${e.error}`)),console.log(``);try{let e=n(),t=((await S.stat(e)).size/1024/1024).toFixed(2);console.log(`Database: ${e} (${t} MB)`)}catch{console.log(`Database: Not found or not accessible`)}console.log(`─`.repeat(50)),process.exit(0)}catch(e){console.error(`Error getting status:`,e),process.exit(1)}}),
|
|
563
|
+
✓ HTTP server started in background`),console.log(`Use "log-sink-mcp stop" to stop the HTTP server`),t&&await t.release().catch(()=>void 0),process.exit(0)}catch(e){t&&await t.release().catch(()=>void 0),console.error(`Error starting services:`,e),process.exit(1)}}),Ye=new _(`status`).description(`Show status of HTTP server and diagnostics`).action(async()=>{try{console.log(`📊 log-sink-mcp Status
|
|
564
|
+
`),console.log(`─`.repeat(50));let e=await f().get(c.HttpServerManager).getStatus();e.running?(console.log(`HTTP Server: 🟢 Running`),console.log(` PID: ${e.pid}`),console.log(` Port: ${e.port}`),console.log(` Health: http://localhost:${e.port}/health`)):(console.log(`HTTP Server: 🔴 Not Running`),e.error&&console.log(` Error: ${e.error}`)),console.log(``);try{let e=n(),t=((await S.stat(e)).size/1024/1024).toFixed(2);console.log(`Database: ${e} (${t} MB)`)}catch{console.log(`Database: Not found or not accessible`)}console.log(`─`.repeat(50)),process.exit(0)}catch(e){console.error(`Error getting status:`,e),process.exit(1)}}),Xe=new _(`stop`).description(`Stop HTTP server and clean up registry/PID files`).action(async()=>{try{console.log(`🛑 Stopping log-sink-mcp services...`),await f().get(c.HttpServerManager).stop()?(console.log(`✓ HTTP server stopped`),console.log(`✓ Registry and PID files cleaned up`)):console.log(`ℹ No HTTP server was running`),console.log(`Done! 👋`),process.exit(0)}catch(e){console.error(`Error stopping services:`,e),process.exit(1)}}),Ze=m(g(import.meta.url)),Qe=JSON.parse(p(h(Ze,`../package.json`),`utf-8`));async function $e(){let e=new _;e.name(`log-sink-mcp`).description(`Log sink MCP server with HTTP ingestion and AI analysis`).version(Qe.version),e.addCommand(Je),e.addCommand(Xe),e.addCommand(Ye),e.addCommand(Ne),e.addCommand(qe),e.addCommand(Ge),await e.parseAsync(process.argv)}$e();export{};
|
|
565
565
|
//# sourceMappingURL=cli.mjs.map
|