@loghead/core 0.1.17 ā 0.1.19
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/api/server.js +84 -0
- package/dist/cli_main.js +2 -13
- package/dist/ui/main.js +1 -1
- package/package.json +1 -1
- package/src/api/server.ts +84 -0
- package/src/cli_main.ts +2 -13
- package/src/ui/main.ts +1 -1
- package/tests/embedding_perf.ts +62 -0
package/dist/api/server.js
CHANGED
|
@@ -17,6 +17,90 @@ async function startApiServer(db) {
|
|
|
17
17
|
await auth.initialize();
|
|
18
18
|
console.log(chalk_1.default.bold.green(`\nš» MCP server running on:`));
|
|
19
19
|
console.log(chalk_1.default.green(`http://localhost:${port}`));
|
|
20
|
+
// Helper to parse OTLP attributes
|
|
21
|
+
const parseOtlpAttributes = (attributes) => {
|
|
22
|
+
if (!Array.isArray(attributes))
|
|
23
|
+
return {};
|
|
24
|
+
const result = {};
|
|
25
|
+
for (const attr of attributes) {
|
|
26
|
+
if (attr.key && attr.value) {
|
|
27
|
+
// Extract value based on type (stringValue, intValue, boolValue, etc.)
|
|
28
|
+
const val = attr.value;
|
|
29
|
+
if (val.stringValue !== undefined)
|
|
30
|
+
result[attr.key] = val.stringValue;
|
|
31
|
+
else if (val.intValue !== undefined)
|
|
32
|
+
result[attr.key] = parseInt(val.intValue);
|
|
33
|
+
else if (val.doubleValue !== undefined)
|
|
34
|
+
result[attr.key] = val.doubleValue;
|
|
35
|
+
else if (val.boolValue !== undefined)
|
|
36
|
+
result[attr.key] = val.boolValue;
|
|
37
|
+
else if (val.arrayValue !== undefined)
|
|
38
|
+
result[attr.key] = val.arrayValue; // Simplified
|
|
39
|
+
else if (val.kvlistValue !== undefined)
|
|
40
|
+
result[attr.key] = val.kvlistValue; // Simplified
|
|
41
|
+
else
|
|
42
|
+
result[attr.key] = val;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
// OTLP Logs Ingestion Endpoint
|
|
48
|
+
app.post("/v1/logs", async (req, res) => {
|
|
49
|
+
try {
|
|
50
|
+
const authHeader = req.headers.authorization;
|
|
51
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
52
|
+
return res.status(401).json({ code: 16, message: "Unauthenticated" });
|
|
53
|
+
}
|
|
54
|
+
const token = authHeader.split(" ")[1];
|
|
55
|
+
const payload = await auth.verifyToken(token);
|
|
56
|
+
if (!payload || !payload.streamId) {
|
|
57
|
+
return res.status(401).json({ code: 16, message: "Invalid token" });
|
|
58
|
+
}
|
|
59
|
+
const streamId = payload.streamId;
|
|
60
|
+
const { resourceLogs } = req.body;
|
|
61
|
+
if (!resourceLogs || !Array.isArray(resourceLogs)) {
|
|
62
|
+
return res.status(400).json({ code: 3, message: "Invalid payload" });
|
|
63
|
+
}
|
|
64
|
+
let count = 0;
|
|
65
|
+
for (const resourceLog of resourceLogs) {
|
|
66
|
+
const resourceAttrs = parseOtlpAttributes(resourceLog.resource?.attributes);
|
|
67
|
+
if (resourceLog.scopeLogs) {
|
|
68
|
+
for (const scopeLog of resourceLog.scopeLogs) {
|
|
69
|
+
const scopeName = scopeLog.scope?.name;
|
|
70
|
+
if (scopeLog.logRecords) {
|
|
71
|
+
for (const log of scopeLog.logRecords) {
|
|
72
|
+
let content = "";
|
|
73
|
+
if (log.body?.stringValue)
|
|
74
|
+
content = log.body.stringValue;
|
|
75
|
+
else if (log.body?.kvlistValue)
|
|
76
|
+
content = JSON.stringify(log.body.kvlistValue);
|
|
77
|
+
else if (typeof log.body === 'string')
|
|
78
|
+
content = log.body; // Fallback
|
|
79
|
+
const logAttrs = parseOtlpAttributes(log.attributes);
|
|
80
|
+
// Merge attributes: Resource > Scope (if any) > Log
|
|
81
|
+
const metadata = {
|
|
82
|
+
...resourceAttrs,
|
|
83
|
+
...logAttrs,
|
|
84
|
+
severity: log.severityText || log.severityNumber,
|
|
85
|
+
scope: scopeName,
|
|
86
|
+
timestamp: log.timeUnixNano
|
|
87
|
+
};
|
|
88
|
+
if (content) {
|
|
89
|
+
await db.addLog(streamId, content, metadata);
|
|
90
|
+
count++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
res.json({ partialSuccess: {}, logsIngested: count });
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
console.error("OTLP Ingest error:", e);
|
|
101
|
+
res.status(500).json({ code: 13, message: String(e) });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
20
104
|
app.post("/api/ingest", async (req, res) => {
|
|
21
105
|
try {
|
|
22
106
|
const authHeader = req.headers.authorization;
|
package/dist/cli_main.js
CHANGED
|
@@ -16,10 +16,6 @@ const db = new db_1.DbService();
|
|
|
16
16
|
const auth = new auth_1.AuthService();
|
|
17
17
|
async function main() {
|
|
18
18
|
const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
19
|
-
.command("init", "Initialize/Migrate database", {}, async () => {
|
|
20
|
-
console.log("Initializing database...");
|
|
21
|
-
await (0, migrate_1.migrate)();
|
|
22
|
-
})
|
|
23
19
|
.command(["start", "$0"], "Start API Server", {}, async () => {
|
|
24
20
|
console.log("Ensuring database is initialized...");
|
|
25
21
|
await (0, migrate_1.migrate)(false); // Run migrations silently
|
|
@@ -29,11 +25,6 @@ async function main() {
|
|
|
29
25
|
// Start TUI (this will clear screen and take over)
|
|
30
26
|
await (0, main_1.startTui)(db, token);
|
|
31
27
|
process.exit(0);
|
|
32
|
-
})
|
|
33
|
-
.command("ui", "Start Terminal UI", {}, async () => {
|
|
34
|
-
const token = await auth.getOrCreateMcpToken();
|
|
35
|
-
await (0, main_1.startTui)(db, token);
|
|
36
|
-
process.exit(0);
|
|
37
28
|
})
|
|
38
29
|
.command("projects <cmd> [name]", "Manage projects", (yargs) => {
|
|
39
30
|
yargs
|
|
@@ -70,10 +61,8 @@ async function main() {
|
|
|
70
61
|
console.log(`Stream created: ${s.id}`);
|
|
71
62
|
console.log(`Token: ${s.token}`);
|
|
72
63
|
})
|
|
73
|
-
.command("token", "Get token for stream", {
|
|
74
|
-
|
|
75
|
-
}, async (argv) => {
|
|
76
|
-
const token = await auth.createStreamToken(argv.stream);
|
|
64
|
+
.command("token <streamId>", "Get token for stream", {}, async (argv) => {
|
|
65
|
+
const token = await auth.createStreamToken(argv.streamId);
|
|
77
66
|
console.log(`Token: ${token}`);
|
|
78
67
|
})
|
|
79
68
|
.command("delete <id>", "Delete stream", {}, (argv) => {
|
package/dist/ui/main.js
CHANGED
package/package.json
CHANGED
package/src/api/server.ts
CHANGED
|
@@ -18,6 +18,90 @@ export async function startApiServer(db: DbService) {
|
|
|
18
18
|
console.log(chalk.bold.green(`\nš» MCP server running on:`));
|
|
19
19
|
console.log(chalk.green(`http://localhost:${port}`));
|
|
20
20
|
|
|
21
|
+
// Helper to parse OTLP attributes
|
|
22
|
+
const parseOtlpAttributes = (attributes: any[]) => {
|
|
23
|
+
if (!Array.isArray(attributes)) return {};
|
|
24
|
+
const result: Record<string, any> = {};
|
|
25
|
+
for (const attr of attributes) {
|
|
26
|
+
if (attr.key && attr.value) {
|
|
27
|
+
// Extract value based on type (stringValue, intValue, boolValue, etc.)
|
|
28
|
+
const val = attr.value;
|
|
29
|
+
if (val.stringValue !== undefined) result[attr.key] = val.stringValue;
|
|
30
|
+
else if (val.intValue !== undefined) result[attr.key] = parseInt(val.intValue);
|
|
31
|
+
else if (val.doubleValue !== undefined) result[attr.key] = val.doubleValue;
|
|
32
|
+
else if (val.boolValue !== undefined) result[attr.key] = val.boolValue;
|
|
33
|
+
else if (val.arrayValue !== undefined) result[attr.key] = val.arrayValue; // Simplified
|
|
34
|
+
else if (val.kvlistValue !== undefined) result[attr.key] = val.kvlistValue; // Simplified
|
|
35
|
+
else result[attr.key] = val;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// OTLP Logs Ingestion Endpoint
|
|
42
|
+
app.post("/v1/logs", async (req, res) => {
|
|
43
|
+
try {
|
|
44
|
+
const authHeader = req.headers.authorization;
|
|
45
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
46
|
+
return res.status(401).json({ code: 16, message: "Unauthenticated" });
|
|
47
|
+
}
|
|
48
|
+
const token = authHeader.split(" ")[1];
|
|
49
|
+
const payload = await auth.verifyToken(token);
|
|
50
|
+
if (!payload || !payload.streamId) {
|
|
51
|
+
return res.status(401).json({ code: 16, message: "Invalid token" });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const streamId = payload.streamId;
|
|
55
|
+
const { resourceLogs } = req.body;
|
|
56
|
+
|
|
57
|
+
if (!resourceLogs || !Array.isArray(resourceLogs)) {
|
|
58
|
+
return res.status(400).json({ code: 3, message: "Invalid payload" });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let count = 0;
|
|
62
|
+
|
|
63
|
+
for (const resourceLog of resourceLogs) {
|
|
64
|
+
const resourceAttrs = parseOtlpAttributes(resourceLog.resource?.attributes);
|
|
65
|
+
|
|
66
|
+
if (resourceLog.scopeLogs) {
|
|
67
|
+
for (const scopeLog of resourceLog.scopeLogs) {
|
|
68
|
+
const scopeName = scopeLog.scope?.name;
|
|
69
|
+
|
|
70
|
+
if (scopeLog.logRecords) {
|
|
71
|
+
for (const log of scopeLog.logRecords) {
|
|
72
|
+
let content = "";
|
|
73
|
+
if (log.body?.stringValue) content = log.body.stringValue;
|
|
74
|
+
else if (log.body?.kvlistValue) content = JSON.stringify(log.body.kvlistValue);
|
|
75
|
+
else if (typeof log.body === 'string') content = log.body; // Fallback
|
|
76
|
+
|
|
77
|
+
const logAttrs = parseOtlpAttributes(log.attributes);
|
|
78
|
+
|
|
79
|
+
// Merge attributes: Resource > Scope (if any) > Log
|
|
80
|
+
const metadata = {
|
|
81
|
+
...resourceAttrs,
|
|
82
|
+
...logAttrs,
|
|
83
|
+
severity: log.severityText || log.severityNumber,
|
|
84
|
+
scope: scopeName,
|
|
85
|
+
timestamp: log.timeUnixNano
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (content) {
|
|
89
|
+
await db.addLog(streamId, content, metadata);
|
|
90
|
+
count++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
res.json({ partialSuccess: {}, logsIngested: count });
|
|
99
|
+
} catch (e) {
|
|
100
|
+
console.error("OTLP Ingest error:", e);
|
|
101
|
+
res.status(500).json({ code: 13, message: String(e) });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
21
105
|
app.post("/api/ingest", async (req, res) => {
|
|
22
106
|
try {
|
|
23
107
|
const authHeader = req.headers.authorization;
|
package/src/cli_main.ts
CHANGED
|
@@ -14,10 +14,6 @@ const auth = new AuthService();
|
|
|
14
14
|
|
|
15
15
|
async function main() {
|
|
16
16
|
const argv = await yargs(hideBin(process.argv))
|
|
17
|
-
.command("init", "Initialize/Migrate database", {}, async () => {
|
|
18
|
-
console.log("Initializing database...");
|
|
19
|
-
await migrate();
|
|
20
|
-
})
|
|
21
17
|
.command(["start", "$0"], "Start API Server", {}, async () => {
|
|
22
18
|
console.log("Ensuring database is initialized...");
|
|
23
19
|
await migrate(false); // Run migrations silently
|
|
@@ -31,11 +27,6 @@ async function main() {
|
|
|
31
27
|
await startTui(db, token);
|
|
32
28
|
process.exit(0);
|
|
33
29
|
})
|
|
34
|
-
.command("ui", "Start Terminal UI", {}, async () => {
|
|
35
|
-
const token = await auth.getOrCreateMcpToken();
|
|
36
|
-
await startTui(db, token);
|
|
37
|
-
process.exit(0);
|
|
38
|
-
})
|
|
39
30
|
.command("projects <cmd> [name]", "Manage projects", (yargs) => {
|
|
40
31
|
yargs
|
|
41
32
|
.command("list", "List projects", {}, () => {
|
|
@@ -71,10 +62,8 @@ async function main() {
|
|
|
71
62
|
console.log(`Stream created: ${s.id}`);
|
|
72
63
|
console.log(`Token: ${s.token}`);
|
|
73
64
|
})
|
|
74
|
-
.command("token", "Get token for stream", {
|
|
75
|
-
|
|
76
|
-
}, async (argv) => {
|
|
77
|
-
const token = await auth.createStreamToken(argv.stream as string);
|
|
65
|
+
.command("token <streamId>", "Get token for stream", {}, async (argv) => {
|
|
66
|
+
const token = await auth.createStreamToken(argv.streamId as string);
|
|
78
67
|
console.log(`Token: ${token}`);
|
|
79
68
|
})
|
|
80
69
|
.command("delete <id>", "Delete stream", {}, (argv) => {
|
package/src/ui/main.ts
CHANGED
|
@@ -101,7 +101,7 @@ export async function startTui(db: DbService, token: string) {
|
|
|
101
101
|
type: "list",
|
|
102
102
|
name: "type",
|
|
103
103
|
message: "Stream type:",
|
|
104
|
-
choices: ["browser", "terminal", "docker"],
|
|
104
|
+
choices: ["browser", "terminal", "docker", "opentelemetry"],
|
|
105
105
|
prefix: "š”"
|
|
106
106
|
}
|
|
107
107
|
]);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { OllamaService } from "../src/services/ollama";
|
|
2
|
+
import { performance } from "perf_hooks";
|
|
3
|
+
|
|
4
|
+
async function runTest() {
|
|
5
|
+
console.log("Starting Embedding Performance Test...");
|
|
6
|
+
|
|
7
|
+
const service = new OllamaService();
|
|
8
|
+
|
|
9
|
+
// Ensure model exists first so pull time doesn't affect metrics
|
|
10
|
+
console.log("Checking model availability...");
|
|
11
|
+
await service.ensureModel();
|
|
12
|
+
|
|
13
|
+
const testString = "This is a test log message that simulates a typical log entry in a production system. It contains some details about an error or an event that occurred.";
|
|
14
|
+
const iterations = 10;
|
|
15
|
+
const latencies: number[] = [];
|
|
16
|
+
|
|
17
|
+
console.log(`Running ${iterations} iterations...`);
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < iterations; i++) {
|
|
20
|
+
const start = performance.now();
|
|
21
|
+
try {
|
|
22
|
+
await service.generateEmbedding(testString);
|
|
23
|
+
const end = performance.now();
|
|
24
|
+
const duration = end - start;
|
|
25
|
+
latencies.push(duration);
|
|
26
|
+
console.log(`Iteration ${i + 1}: ${duration.toFixed(2)}ms`);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.error(`Iteration ${i + 1} failed:`, e);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (latencies.length > 0) {
|
|
33
|
+
const warmUp = latencies[0];
|
|
34
|
+
const results = latencies.slice(1); // Remove warm-up
|
|
35
|
+
|
|
36
|
+
console.log("\n--- Results ---");
|
|
37
|
+
console.log(`Total Iterations: ${latencies.length}`);
|
|
38
|
+
console.log(`Warm-up Time: ${warmUp.toFixed(2)}ms`);
|
|
39
|
+
|
|
40
|
+
if (results.length > 0) {
|
|
41
|
+
const min = Math.min(...results);
|
|
42
|
+
const max = Math.max(...results);
|
|
43
|
+
|
|
44
|
+
// Calculate Median
|
|
45
|
+
results.sort((a, b) => a - b);
|
|
46
|
+
const mid = Math.floor(results.length / 2);
|
|
47
|
+
const median = results.length % 2 !== 0
|
|
48
|
+
? results[mid]
|
|
49
|
+
: (results[mid - 1] + results[mid]) / 2;
|
|
50
|
+
|
|
51
|
+
console.log(`Min Latency (excl. warmup): ${min.toFixed(2)}ms`);
|
|
52
|
+
console.log(`Avg Latency (excl. warmup): ${median.toFixed(2)}ms`);
|
|
53
|
+
console.log(`Max Latency (excl. warmup): ${max.toFixed(2)}ms`);
|
|
54
|
+
} else {
|
|
55
|
+
console.log("Not enough iterations to calculate stats (need > 1).");
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
console.log("\nNo successful iterations.");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
runTest().catch(console.error);
|