@loghead/core 0.1.18 → 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.
@@ -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/ui/main.js CHANGED
@@ -94,7 +94,7 @@ async function startTui(db, token) {
94
94
  type: "list",
95
95
  name: "type",
96
96
  message: "Stream type:",
97
- choices: ["browser", "terminal", "docker"],
97
+ choices: ["browser", "terminal", "docker", "opentelemetry"],
98
98
  prefix: "šŸ’”"
99
99
  }
100
100
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loghead/core",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Core API and Database for Loghead",
5
5
  "repository": {
6
6
  "type": "git",
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/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);