@ydking0911/observr 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/index.d.mts +128 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.js +420 -0
- package/dist/index.mjs +399 -0
- package/package.json +63 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
interface ObservrEvent {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
type: "http_request" | "span" | "log";
|
|
4
|
+
level: "debug" | "info" | "warn" | "error";
|
|
5
|
+
trace_id?: string;
|
|
6
|
+
span_id?: string;
|
|
7
|
+
message: string;
|
|
8
|
+
service?: string;
|
|
9
|
+
duration_ms?: number;
|
|
10
|
+
method?: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
status_code?: number;
|
|
13
|
+
attributes?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
interface ObservrConfig {
|
|
16
|
+
service?: string;
|
|
17
|
+
collectorUrl?: string;
|
|
18
|
+
autoInstrument?: boolean;
|
|
19
|
+
logLevel?: "debug" | "info" | "warn" | "error";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare class Transport {
|
|
23
|
+
private readonly collectorUrl;
|
|
24
|
+
private readonly service;
|
|
25
|
+
private queue;
|
|
26
|
+
private timer;
|
|
27
|
+
private stopped;
|
|
28
|
+
constructor(collectorUrl: string, service: string);
|
|
29
|
+
send(event: Omit<ObservrEvent, "service"> & {
|
|
30
|
+
service?: string;
|
|
31
|
+
}): void;
|
|
32
|
+
flush(timeoutMs?: number): Promise<void>;
|
|
33
|
+
shutdown(): Promise<void>;
|
|
34
|
+
private _flush;
|
|
35
|
+
private _postBatch;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare class Span {
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly spanId: string;
|
|
41
|
+
readonly traceId: string;
|
|
42
|
+
private readonly transport;
|
|
43
|
+
private readonly attributes;
|
|
44
|
+
private startTime;
|
|
45
|
+
constructor(name: string, transport: Transport, attributes?: Record<string, unknown>);
|
|
46
|
+
setAttribute(key: string, value: unknown): this;
|
|
47
|
+
/** Run an async function inside this span, emit on completion. */
|
|
48
|
+
run<T>(fn: (span: this) => Promise<T>): Promise<T>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
declare class ObservrClient {
|
|
52
|
+
readonly service: string;
|
|
53
|
+
readonly collectorUrl: string;
|
|
54
|
+
readonly transport: Transport;
|
|
55
|
+
private readonly config;
|
|
56
|
+
private started;
|
|
57
|
+
constructor(config: Required<ObservrConfig>);
|
|
58
|
+
start(): void;
|
|
59
|
+
/** Wrap an async function in a named span. */
|
|
60
|
+
span(name: string, attributes?: Record<string, unknown>): Span;
|
|
61
|
+
shutdown(): Promise<void>;
|
|
62
|
+
private _autoInstrument;
|
|
63
|
+
private _instrumentExpress;
|
|
64
|
+
private _instrumentFastify;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type Req = {
|
|
68
|
+
method: string;
|
|
69
|
+
path: string;
|
|
70
|
+
url: string;
|
|
71
|
+
ip?: string;
|
|
72
|
+
headers: Record<string, string | string[] | undefined>;
|
|
73
|
+
};
|
|
74
|
+
type Res = {
|
|
75
|
+
statusCode: number;
|
|
76
|
+
on: (event: string, fn: () => void) => void;
|
|
77
|
+
};
|
|
78
|
+
type Next = () => void;
|
|
79
|
+
/**
|
|
80
|
+
* Express middleware factory.
|
|
81
|
+
* Usage:
|
|
82
|
+
* import { expressMiddleware } from "observr";
|
|
83
|
+
* app.use(expressMiddleware(transport));
|
|
84
|
+
*/
|
|
85
|
+
declare function expressMiddleware(transport: Transport): (req: Req, res: Res, next: Next) => void;
|
|
86
|
+
|
|
87
|
+
type FastifyInstance = any;
|
|
88
|
+
/**
|
|
89
|
+
* Fastify plugin that records HTTP request events via observr.
|
|
90
|
+
*
|
|
91
|
+
* Usage (manual registration):
|
|
92
|
+
* import { fastifyPlugin } from "observr";
|
|
93
|
+
* await app.register(fastifyPlugin(transport));
|
|
94
|
+
*
|
|
95
|
+
* The plugin sets `Symbol.for('skip-override')` so it applies to the
|
|
96
|
+
* root scope and captures all routes regardless of encapsulation.
|
|
97
|
+
*/
|
|
98
|
+
declare function fastifyPlugin(transport: Transport): (app: FastifyInstance, _options: unknown) => Promise<void>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* observr — Zero-config observability for AI agents and developers.
|
|
102
|
+
*
|
|
103
|
+
* Usage:
|
|
104
|
+
* import observr from "@ydking0911/observr";
|
|
105
|
+
* observr.init({ service: "my-api" });
|
|
106
|
+
*
|
|
107
|
+
* // Manual span
|
|
108
|
+
* await observr.span("db.query").run(async (span) => {
|
|
109
|
+
* const rows = await db.query("SELECT ...");
|
|
110
|
+
* span.setAttribute("row_count", rows.length);
|
|
111
|
+
* return rows;
|
|
112
|
+
* });
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Initialize observr. Call once at application startup.
|
|
117
|
+
*/
|
|
118
|
+
declare function init(config?: ObservrConfig): ObservrClient;
|
|
119
|
+
/**
|
|
120
|
+
* Return the active client. Throws if init() has not been called.
|
|
121
|
+
*/
|
|
122
|
+
declare function getClient(): ObservrClient;
|
|
123
|
+
declare const observr: {
|
|
124
|
+
init: typeof init;
|
|
125
|
+
getClient: typeof getClient;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export { ObservrClient, type ObservrConfig, type ObservrEvent, Span, Transport, observr as default, expressMiddleware, fastifyPlugin, getClient, init };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
interface ObservrEvent {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
type: "http_request" | "span" | "log";
|
|
4
|
+
level: "debug" | "info" | "warn" | "error";
|
|
5
|
+
trace_id?: string;
|
|
6
|
+
span_id?: string;
|
|
7
|
+
message: string;
|
|
8
|
+
service?: string;
|
|
9
|
+
duration_ms?: number;
|
|
10
|
+
method?: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
status_code?: number;
|
|
13
|
+
attributes?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
interface ObservrConfig {
|
|
16
|
+
service?: string;
|
|
17
|
+
collectorUrl?: string;
|
|
18
|
+
autoInstrument?: boolean;
|
|
19
|
+
logLevel?: "debug" | "info" | "warn" | "error";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare class Transport {
|
|
23
|
+
private readonly collectorUrl;
|
|
24
|
+
private readonly service;
|
|
25
|
+
private queue;
|
|
26
|
+
private timer;
|
|
27
|
+
private stopped;
|
|
28
|
+
constructor(collectorUrl: string, service: string);
|
|
29
|
+
send(event: Omit<ObservrEvent, "service"> & {
|
|
30
|
+
service?: string;
|
|
31
|
+
}): void;
|
|
32
|
+
flush(timeoutMs?: number): Promise<void>;
|
|
33
|
+
shutdown(): Promise<void>;
|
|
34
|
+
private _flush;
|
|
35
|
+
private _postBatch;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare class Span {
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly spanId: string;
|
|
41
|
+
readonly traceId: string;
|
|
42
|
+
private readonly transport;
|
|
43
|
+
private readonly attributes;
|
|
44
|
+
private startTime;
|
|
45
|
+
constructor(name: string, transport: Transport, attributes?: Record<string, unknown>);
|
|
46
|
+
setAttribute(key: string, value: unknown): this;
|
|
47
|
+
/** Run an async function inside this span, emit on completion. */
|
|
48
|
+
run<T>(fn: (span: this) => Promise<T>): Promise<T>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
declare class ObservrClient {
|
|
52
|
+
readonly service: string;
|
|
53
|
+
readonly collectorUrl: string;
|
|
54
|
+
readonly transport: Transport;
|
|
55
|
+
private readonly config;
|
|
56
|
+
private started;
|
|
57
|
+
constructor(config: Required<ObservrConfig>);
|
|
58
|
+
start(): void;
|
|
59
|
+
/** Wrap an async function in a named span. */
|
|
60
|
+
span(name: string, attributes?: Record<string, unknown>): Span;
|
|
61
|
+
shutdown(): Promise<void>;
|
|
62
|
+
private _autoInstrument;
|
|
63
|
+
private _instrumentExpress;
|
|
64
|
+
private _instrumentFastify;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type Req = {
|
|
68
|
+
method: string;
|
|
69
|
+
path: string;
|
|
70
|
+
url: string;
|
|
71
|
+
ip?: string;
|
|
72
|
+
headers: Record<string, string | string[] | undefined>;
|
|
73
|
+
};
|
|
74
|
+
type Res = {
|
|
75
|
+
statusCode: number;
|
|
76
|
+
on: (event: string, fn: () => void) => void;
|
|
77
|
+
};
|
|
78
|
+
type Next = () => void;
|
|
79
|
+
/**
|
|
80
|
+
* Express middleware factory.
|
|
81
|
+
* Usage:
|
|
82
|
+
* import { expressMiddleware } from "observr";
|
|
83
|
+
* app.use(expressMiddleware(transport));
|
|
84
|
+
*/
|
|
85
|
+
declare function expressMiddleware(transport: Transport): (req: Req, res: Res, next: Next) => void;
|
|
86
|
+
|
|
87
|
+
type FastifyInstance = any;
|
|
88
|
+
/**
|
|
89
|
+
* Fastify plugin that records HTTP request events via observr.
|
|
90
|
+
*
|
|
91
|
+
* Usage (manual registration):
|
|
92
|
+
* import { fastifyPlugin } from "observr";
|
|
93
|
+
* await app.register(fastifyPlugin(transport));
|
|
94
|
+
*
|
|
95
|
+
* The plugin sets `Symbol.for('skip-override')` so it applies to the
|
|
96
|
+
* root scope and captures all routes regardless of encapsulation.
|
|
97
|
+
*/
|
|
98
|
+
declare function fastifyPlugin(transport: Transport): (app: FastifyInstance, _options: unknown) => Promise<void>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* observr — Zero-config observability for AI agents and developers.
|
|
102
|
+
*
|
|
103
|
+
* Usage:
|
|
104
|
+
* import observr from "@ydking0911/observr";
|
|
105
|
+
* observr.init({ service: "my-api" });
|
|
106
|
+
*
|
|
107
|
+
* // Manual span
|
|
108
|
+
* await observr.span("db.query").run(async (span) => {
|
|
109
|
+
* const rows = await db.query("SELECT ...");
|
|
110
|
+
* span.setAttribute("row_count", rows.length);
|
|
111
|
+
* return rows;
|
|
112
|
+
* });
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Initialize observr. Call once at application startup.
|
|
117
|
+
*/
|
|
118
|
+
declare function init(config?: ObservrConfig): ObservrClient;
|
|
119
|
+
/**
|
|
120
|
+
* Return the active client. Throws if init() has not been called.
|
|
121
|
+
*/
|
|
122
|
+
declare function getClient(): ObservrClient;
|
|
123
|
+
declare const observr: {
|
|
124
|
+
init: typeof init;
|
|
125
|
+
getClient: typeof getClient;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export { ObservrClient, type ObservrConfig, type ObservrEvent, Span, Transport, observr as default, expressMiddleware, fastifyPlugin, getClient, init };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/integrations/express.ts
|
|
34
|
+
var express_exports = {};
|
|
35
|
+
__export(express_exports, {
|
|
36
|
+
expressMiddleware: () => expressMiddleware,
|
|
37
|
+
instrumentExpress: () => instrumentExpress
|
|
38
|
+
});
|
|
39
|
+
function expressMiddleware(transport) {
|
|
40
|
+
return function observrMiddleware(req, res, next) {
|
|
41
|
+
const traceId = (0, import_node_crypto2.randomBytes)(16).toString("hex");
|
|
42
|
+
const spanId = (0, import_node_crypto2.randomBytes)(8).toString("hex");
|
|
43
|
+
const start = performance.now();
|
|
44
|
+
res.on("finish", () => {
|
|
45
|
+
const durationMs = parseFloat((performance.now() - start).toFixed(2));
|
|
46
|
+
const statusCode = res.statusCode;
|
|
47
|
+
const level = statusCode >= 500 ? "error" : statusCode >= 400 ? "warn" : "info";
|
|
48
|
+
const path = req.path || req.url || "/";
|
|
49
|
+
transport.send({
|
|
50
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
51
|
+
type: "http_request",
|
|
52
|
+
level,
|
|
53
|
+
trace_id: traceId,
|
|
54
|
+
span_id: spanId,
|
|
55
|
+
message: `${req.method} ${path}`,
|
|
56
|
+
method: req.method,
|
|
57
|
+
path,
|
|
58
|
+
status_code: statusCode,
|
|
59
|
+
duration_ms: durationMs,
|
|
60
|
+
attributes: {
|
|
61
|
+
remote_addr: req.ip,
|
|
62
|
+
user_agent: req.headers["user-agent"]
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
next();
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function instrumentExpress(transport) {
|
|
70
|
+
try {
|
|
71
|
+
const express = require("express");
|
|
72
|
+
if (!express || express.__observr_patched) return;
|
|
73
|
+
const originalExpress = express.default ?? express;
|
|
74
|
+
const mw = expressMiddleware(transport);
|
|
75
|
+
const wrapped = function(...args) {
|
|
76
|
+
const app = originalExpress(...args);
|
|
77
|
+
app.use(mw);
|
|
78
|
+
return app;
|
|
79
|
+
};
|
|
80
|
+
Object.assign(wrapped, originalExpress);
|
|
81
|
+
wrapped.__observr_patched = true;
|
|
82
|
+
if (express.default) {
|
|
83
|
+
express.default = wrapped;
|
|
84
|
+
} else {
|
|
85
|
+
const Module = require("module");
|
|
86
|
+
const cached = Module._cache[require.resolve("express")];
|
|
87
|
+
if (cached) cached.exports = wrapped;
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
var import_node_crypto2;
|
|
93
|
+
var init_express = __esm({
|
|
94
|
+
"src/integrations/express.ts"() {
|
|
95
|
+
"use strict";
|
|
96
|
+
import_node_crypto2 = require("crypto");
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// src/integrations/fastify.ts
|
|
101
|
+
var fastify_exports = {};
|
|
102
|
+
__export(fastify_exports, {
|
|
103
|
+
fastifyPlugin: () => fastifyPlugin,
|
|
104
|
+
instrumentFastify: () => instrumentFastify
|
|
105
|
+
});
|
|
106
|
+
function fastifyPlugin(transport) {
|
|
107
|
+
async function plugin(app, _options) {
|
|
108
|
+
app.addHook(
|
|
109
|
+
"onResponse",
|
|
110
|
+
async (request, reply) => {
|
|
111
|
+
const statusCode = reply.statusCode;
|
|
112
|
+
const durationMs = parseFloat((reply.elapsedTime ?? 0).toFixed(2));
|
|
113
|
+
const level = statusCode >= 500 ? "error" : statusCode >= 400 ? "warn" : "info";
|
|
114
|
+
const path = request.routeOptions?.url ?? request.routerPath ?? request.url?.split("?")[0] ?? "/";
|
|
115
|
+
transport.send({
|
|
116
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
117
|
+
type: "http_request",
|
|
118
|
+
level,
|
|
119
|
+
trace_id: (0, import_node_crypto3.randomBytes)(16).toString("hex"),
|
|
120
|
+
span_id: (0, import_node_crypto3.randomBytes)(8).toString("hex"),
|
|
121
|
+
message: `${request.method} ${path}`,
|
|
122
|
+
method: request.method,
|
|
123
|
+
path,
|
|
124
|
+
status_code: statusCode,
|
|
125
|
+
duration_ms: durationMs,
|
|
126
|
+
attributes: {
|
|
127
|
+
remote_addr: request.ip,
|
|
128
|
+
user_agent: request.headers?.["user-agent"]
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
plugin[/* @__PURE__ */ Symbol.for("skip-override")] = true;
|
|
135
|
+
return plugin;
|
|
136
|
+
}
|
|
137
|
+
function instrumentFastify(transport) {
|
|
138
|
+
try {
|
|
139
|
+
const mod = require("fastify");
|
|
140
|
+
if (!mod || mod.__observr_patched) return;
|
|
141
|
+
const original = mod.default ?? mod;
|
|
142
|
+
const plugin = fastifyPlugin(transport);
|
|
143
|
+
const wrapped = function(...args) {
|
|
144
|
+
const app = original(...args);
|
|
145
|
+
app.register(plugin);
|
|
146
|
+
return app;
|
|
147
|
+
};
|
|
148
|
+
Object.assign(wrapped, original);
|
|
149
|
+
mod.__observr_patched = true;
|
|
150
|
+
const Module = require("module");
|
|
151
|
+
const cached = Module._cache[require.resolve("fastify")];
|
|
152
|
+
if (cached) {
|
|
153
|
+
if (mod.default) {
|
|
154
|
+
mod.default = wrapped;
|
|
155
|
+
cached.exports.default = wrapped;
|
|
156
|
+
} else {
|
|
157
|
+
cached.exports = wrapped;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
var import_node_crypto3;
|
|
164
|
+
var init_fastify = __esm({
|
|
165
|
+
"src/integrations/fastify.ts"() {
|
|
166
|
+
"use strict";
|
|
167
|
+
import_node_crypto3 = require("crypto");
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// src/index.ts
|
|
172
|
+
var index_exports = {};
|
|
173
|
+
__export(index_exports, {
|
|
174
|
+
ObservrClient: () => ObservrClient,
|
|
175
|
+
Span: () => Span,
|
|
176
|
+
Transport: () => Transport,
|
|
177
|
+
default: () => index_default,
|
|
178
|
+
expressMiddleware: () => expressMiddleware,
|
|
179
|
+
fastifyPlugin: () => fastifyPlugin,
|
|
180
|
+
getClient: () => getClient,
|
|
181
|
+
init: () => init
|
|
182
|
+
});
|
|
183
|
+
module.exports = __toCommonJS(index_exports);
|
|
184
|
+
|
|
185
|
+
// src/transport.ts
|
|
186
|
+
var BATCH_SIZE = 50;
|
|
187
|
+
var FLUSH_INTERVAL_MS = 1e3;
|
|
188
|
+
var Transport = class {
|
|
189
|
+
constructor(collectorUrl, service) {
|
|
190
|
+
this.queue = [];
|
|
191
|
+
this.timer = null;
|
|
192
|
+
this.stopped = false;
|
|
193
|
+
this.collectorUrl = collectorUrl.replace(/\/$/, "");
|
|
194
|
+
this.service = service;
|
|
195
|
+
this.timer = setInterval(() => this._flush(), FLUSH_INTERVAL_MS);
|
|
196
|
+
if (this.timer.unref) this.timer.unref();
|
|
197
|
+
}
|
|
198
|
+
send(event) {
|
|
199
|
+
if (this.stopped) return;
|
|
200
|
+
const full = { service: this.service, ...event };
|
|
201
|
+
if (this.queue.length >= 1e4) return;
|
|
202
|
+
this.queue.push(full);
|
|
203
|
+
if (this.queue.length >= BATCH_SIZE) this._flush();
|
|
204
|
+
}
|
|
205
|
+
async flush(timeoutMs = 5e3) {
|
|
206
|
+
const deadline = Date.now() + timeoutMs;
|
|
207
|
+
while (this.queue.length > 0 && Date.now() < deadline) {
|
|
208
|
+
await this._flush();
|
|
209
|
+
if (this.queue.length > 0) await sleep(50);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async shutdown() {
|
|
213
|
+
this.stopped = true;
|
|
214
|
+
if (this.timer) {
|
|
215
|
+
clearInterval(this.timer);
|
|
216
|
+
this.timer = null;
|
|
217
|
+
}
|
|
218
|
+
await this.flush();
|
|
219
|
+
}
|
|
220
|
+
_flush() {
|
|
221
|
+
if (this.queue.length === 0) return Promise.resolve();
|
|
222
|
+
const batch = this.queue.splice(0, BATCH_SIZE);
|
|
223
|
+
return this._postBatch(batch);
|
|
224
|
+
}
|
|
225
|
+
async _postBatch(batch) {
|
|
226
|
+
try {
|
|
227
|
+
const url = `${this.collectorUrl}/events`;
|
|
228
|
+
await fetch(url, {
|
|
229
|
+
method: "POST",
|
|
230
|
+
headers: { "Content-Type": "application/json" },
|
|
231
|
+
body: JSON.stringify({ events: batch }),
|
|
232
|
+
signal: AbortSignal.timeout(3e3)
|
|
233
|
+
});
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
function sleep(ms) {
|
|
239
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/logger.ts
|
|
243
|
+
var LEVEL_RANK = {
|
|
244
|
+
debug: 0,
|
|
245
|
+
info: 1,
|
|
246
|
+
warn: 2,
|
|
247
|
+
error: 3
|
|
248
|
+
};
|
|
249
|
+
var CONSOLE_MAP = {
|
|
250
|
+
debug: "debug",
|
|
251
|
+
info: "log",
|
|
252
|
+
warn: "warn",
|
|
253
|
+
error: "error"
|
|
254
|
+
};
|
|
255
|
+
var originals = /* @__PURE__ */ new Map();
|
|
256
|
+
function patchConsole(transport, minLevel) {
|
|
257
|
+
const levels = ["debug", "info", "warn", "error"];
|
|
258
|
+
for (const level of levels) {
|
|
259
|
+
const consoleKey = CONSOLE_MAP[level];
|
|
260
|
+
const original = console[consoleKey];
|
|
261
|
+
originals.set(level, original);
|
|
262
|
+
console[consoleKey] = (...args) => {
|
|
263
|
+
original.apply(console, args);
|
|
264
|
+
if (LEVEL_RANK[level] >= LEVEL_RANK[minLevel]) {
|
|
265
|
+
const message = args.map(
|
|
266
|
+
(a) => typeof a === "string" ? a : JSON.stringify(a)
|
|
267
|
+
).join(" ");
|
|
268
|
+
transport.send({
|
|
269
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
270
|
+
type: "log",
|
|
271
|
+
level,
|
|
272
|
+
message,
|
|
273
|
+
attributes: {}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function unpatchConsole() {
|
|
280
|
+
for (const [level, original] of originals) {
|
|
281
|
+
const consoleKey = CONSOLE_MAP[level];
|
|
282
|
+
console[consoleKey] = original;
|
|
283
|
+
}
|
|
284
|
+
originals.clear();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/span.ts
|
|
288
|
+
var import_node_crypto = require("crypto");
|
|
289
|
+
var Span = class {
|
|
290
|
+
constructor(name, transport, attributes = {}) {
|
|
291
|
+
this.startTime = 0;
|
|
292
|
+
this.name = name;
|
|
293
|
+
this.spanId = (0, import_node_crypto.randomBytes)(8).toString("hex");
|
|
294
|
+
this.traceId = (0, import_node_crypto.randomBytes)(16).toString("hex");
|
|
295
|
+
this.transport = transport;
|
|
296
|
+
this.attributes = { ...attributes };
|
|
297
|
+
}
|
|
298
|
+
setAttribute(key, value) {
|
|
299
|
+
this.attributes[key] = value;
|
|
300
|
+
return this;
|
|
301
|
+
}
|
|
302
|
+
/** Run an async function inside this span, emit on completion. */
|
|
303
|
+
async run(fn) {
|
|
304
|
+
this.startTime = performance.now();
|
|
305
|
+
let level = "info";
|
|
306
|
+
let exceptionMsg;
|
|
307
|
+
try {
|
|
308
|
+
const result = await fn(this);
|
|
309
|
+
return result;
|
|
310
|
+
} catch (err) {
|
|
311
|
+
level = "error";
|
|
312
|
+
exceptionMsg = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
313
|
+
throw err;
|
|
314
|
+
} finally {
|
|
315
|
+
const durationMs = parseFloat(
|
|
316
|
+
(performance.now() - this.startTime).toFixed(2)
|
|
317
|
+
);
|
|
318
|
+
if (exceptionMsg) this.attributes["exception"] = exceptionMsg;
|
|
319
|
+
this.transport.send({
|
|
320
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
321
|
+
type: "span",
|
|
322
|
+
level,
|
|
323
|
+
trace_id: this.traceId,
|
|
324
|
+
span_id: this.spanId,
|
|
325
|
+
message: this.name,
|
|
326
|
+
duration_ms: durationMs,
|
|
327
|
+
attributes: this.attributes
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// src/client.ts
|
|
334
|
+
var ObservrClient = class {
|
|
335
|
+
constructor(config) {
|
|
336
|
+
this.started = false;
|
|
337
|
+
this.config = config;
|
|
338
|
+
this.service = config.service;
|
|
339
|
+
this.collectorUrl = config.collectorUrl;
|
|
340
|
+
this.transport = new Transport(config.collectorUrl, config.service);
|
|
341
|
+
}
|
|
342
|
+
start() {
|
|
343
|
+
if (this.started) return;
|
|
344
|
+
this.started = true;
|
|
345
|
+
patchConsole(this.transport, this.config.logLevel);
|
|
346
|
+
if (this.config.autoInstrument) {
|
|
347
|
+
this._autoInstrument();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/** Wrap an async function in a named span. */
|
|
351
|
+
span(name, attributes = {}) {
|
|
352
|
+
return new Span(name, this.transport, attributes);
|
|
353
|
+
}
|
|
354
|
+
async shutdown() {
|
|
355
|
+
unpatchConsole();
|
|
356
|
+
await this.transport.shutdown();
|
|
357
|
+
this.started = false;
|
|
358
|
+
}
|
|
359
|
+
_autoInstrument() {
|
|
360
|
+
if (typeof require !== "undefined") {
|
|
361
|
+
try {
|
|
362
|
+
require("express");
|
|
363
|
+
this._instrumentExpress();
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
require("fastify");
|
|
368
|
+
this._instrumentFastify();
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
_instrumentExpress() {
|
|
374
|
+
try {
|
|
375
|
+
const { instrumentExpress: instrumentExpress2 } = (init_express(), __toCommonJS(express_exports));
|
|
376
|
+
instrumentExpress2(this.transport);
|
|
377
|
+
} catch {
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
_instrumentFastify() {
|
|
381
|
+
try {
|
|
382
|
+
const { instrumentFastify: instrumentFastify2 } = (init_fastify(), __toCommonJS(fastify_exports));
|
|
383
|
+
instrumentFastify2(this.transport);
|
|
384
|
+
} catch {
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// src/index.ts
|
|
390
|
+
init_express();
|
|
391
|
+
init_fastify();
|
|
392
|
+
var _client = null;
|
|
393
|
+
function init(config = {}) {
|
|
394
|
+
_client = new ObservrClient({
|
|
395
|
+
service: config.service ?? "app",
|
|
396
|
+
collectorUrl: config.collectorUrl ?? "http://localhost:7676",
|
|
397
|
+
autoInstrument: config.autoInstrument ?? true,
|
|
398
|
+
logLevel: config.logLevel ?? "debug"
|
|
399
|
+
});
|
|
400
|
+
_client.start();
|
|
401
|
+
return _client;
|
|
402
|
+
}
|
|
403
|
+
function getClient() {
|
|
404
|
+
if (!_client) {
|
|
405
|
+
throw new Error("observr.init() must be called before getClient()");
|
|
406
|
+
}
|
|
407
|
+
return _client;
|
|
408
|
+
}
|
|
409
|
+
var observr = { init, getClient };
|
|
410
|
+
var index_default = observr;
|
|
411
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
412
|
+
0 && (module.exports = {
|
|
413
|
+
ObservrClient,
|
|
414
|
+
Span,
|
|
415
|
+
Transport,
|
|
416
|
+
expressMiddleware,
|
|
417
|
+
fastifyPlugin,
|
|
418
|
+
getClient,
|
|
419
|
+
init
|
|
420
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
6
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
7
|
+
}) : x)(function(x) {
|
|
8
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
9
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
+
});
|
|
11
|
+
var __esm = (fn, res) => function __init() {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
};
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
|
|
28
|
+
// src/integrations/express.ts
|
|
29
|
+
var express_exports = {};
|
|
30
|
+
__export(express_exports, {
|
|
31
|
+
expressMiddleware: () => expressMiddleware,
|
|
32
|
+
instrumentExpress: () => instrumentExpress
|
|
33
|
+
});
|
|
34
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
35
|
+
function expressMiddleware(transport) {
|
|
36
|
+
return function observrMiddleware(req, res, next) {
|
|
37
|
+
const traceId = randomBytes2(16).toString("hex");
|
|
38
|
+
const spanId = randomBytes2(8).toString("hex");
|
|
39
|
+
const start = performance.now();
|
|
40
|
+
res.on("finish", () => {
|
|
41
|
+
const durationMs = parseFloat((performance.now() - start).toFixed(2));
|
|
42
|
+
const statusCode = res.statusCode;
|
|
43
|
+
const level = statusCode >= 500 ? "error" : statusCode >= 400 ? "warn" : "info";
|
|
44
|
+
const path = req.path || req.url || "/";
|
|
45
|
+
transport.send({
|
|
46
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47
|
+
type: "http_request",
|
|
48
|
+
level,
|
|
49
|
+
trace_id: traceId,
|
|
50
|
+
span_id: spanId,
|
|
51
|
+
message: `${req.method} ${path}`,
|
|
52
|
+
method: req.method,
|
|
53
|
+
path,
|
|
54
|
+
status_code: statusCode,
|
|
55
|
+
duration_ms: durationMs,
|
|
56
|
+
attributes: {
|
|
57
|
+
remote_addr: req.ip,
|
|
58
|
+
user_agent: req.headers["user-agent"]
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
next();
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function instrumentExpress(transport) {
|
|
66
|
+
try {
|
|
67
|
+
const express = __require("express");
|
|
68
|
+
if (!express || express.__observr_patched) return;
|
|
69
|
+
const originalExpress = express.default ?? express;
|
|
70
|
+
const mw = expressMiddleware(transport);
|
|
71
|
+
const wrapped = function(...args) {
|
|
72
|
+
const app = originalExpress(...args);
|
|
73
|
+
app.use(mw);
|
|
74
|
+
return app;
|
|
75
|
+
};
|
|
76
|
+
Object.assign(wrapped, originalExpress);
|
|
77
|
+
wrapped.__observr_patched = true;
|
|
78
|
+
if (express.default) {
|
|
79
|
+
express.default = wrapped;
|
|
80
|
+
} else {
|
|
81
|
+
const Module = __require("module");
|
|
82
|
+
const cached = Module._cache[__require.resolve("express")];
|
|
83
|
+
if (cached) cached.exports = wrapped;
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
var init_express = __esm({
|
|
89
|
+
"src/integrations/express.ts"() {
|
|
90
|
+
"use strict";
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// src/integrations/fastify.ts
|
|
95
|
+
var fastify_exports = {};
|
|
96
|
+
__export(fastify_exports, {
|
|
97
|
+
fastifyPlugin: () => fastifyPlugin,
|
|
98
|
+
instrumentFastify: () => instrumentFastify
|
|
99
|
+
});
|
|
100
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
101
|
+
function fastifyPlugin(transport) {
|
|
102
|
+
async function plugin(app, _options) {
|
|
103
|
+
app.addHook(
|
|
104
|
+
"onResponse",
|
|
105
|
+
async (request, reply) => {
|
|
106
|
+
const statusCode = reply.statusCode;
|
|
107
|
+
const durationMs = parseFloat((reply.elapsedTime ?? 0).toFixed(2));
|
|
108
|
+
const level = statusCode >= 500 ? "error" : statusCode >= 400 ? "warn" : "info";
|
|
109
|
+
const path = request.routeOptions?.url ?? request.routerPath ?? request.url?.split("?")[0] ?? "/";
|
|
110
|
+
transport.send({
|
|
111
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
112
|
+
type: "http_request",
|
|
113
|
+
level,
|
|
114
|
+
trace_id: randomBytes3(16).toString("hex"),
|
|
115
|
+
span_id: randomBytes3(8).toString("hex"),
|
|
116
|
+
message: `${request.method} ${path}`,
|
|
117
|
+
method: request.method,
|
|
118
|
+
path,
|
|
119
|
+
status_code: statusCode,
|
|
120
|
+
duration_ms: durationMs,
|
|
121
|
+
attributes: {
|
|
122
|
+
remote_addr: request.ip,
|
|
123
|
+
user_agent: request.headers?.["user-agent"]
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
plugin[/* @__PURE__ */ Symbol.for("skip-override")] = true;
|
|
130
|
+
return plugin;
|
|
131
|
+
}
|
|
132
|
+
function instrumentFastify(transport) {
|
|
133
|
+
try {
|
|
134
|
+
const mod = __require("fastify");
|
|
135
|
+
if (!mod || mod.__observr_patched) return;
|
|
136
|
+
const original = mod.default ?? mod;
|
|
137
|
+
const plugin = fastifyPlugin(transport);
|
|
138
|
+
const wrapped = function(...args) {
|
|
139
|
+
const app = original(...args);
|
|
140
|
+
app.register(plugin);
|
|
141
|
+
return app;
|
|
142
|
+
};
|
|
143
|
+
Object.assign(wrapped, original);
|
|
144
|
+
mod.__observr_patched = true;
|
|
145
|
+
const Module = __require("module");
|
|
146
|
+
const cached = Module._cache[__require.resolve("fastify")];
|
|
147
|
+
if (cached) {
|
|
148
|
+
if (mod.default) {
|
|
149
|
+
mod.default = wrapped;
|
|
150
|
+
cached.exports.default = wrapped;
|
|
151
|
+
} else {
|
|
152
|
+
cached.exports = wrapped;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
var init_fastify = __esm({
|
|
159
|
+
"src/integrations/fastify.ts"() {
|
|
160
|
+
"use strict";
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// src/transport.ts
|
|
165
|
+
var BATCH_SIZE = 50;
|
|
166
|
+
var FLUSH_INTERVAL_MS = 1e3;
|
|
167
|
+
var Transport = class {
|
|
168
|
+
constructor(collectorUrl, service) {
|
|
169
|
+
this.queue = [];
|
|
170
|
+
this.timer = null;
|
|
171
|
+
this.stopped = false;
|
|
172
|
+
this.collectorUrl = collectorUrl.replace(/\/$/, "");
|
|
173
|
+
this.service = service;
|
|
174
|
+
this.timer = setInterval(() => this._flush(), FLUSH_INTERVAL_MS);
|
|
175
|
+
if (this.timer.unref) this.timer.unref();
|
|
176
|
+
}
|
|
177
|
+
send(event) {
|
|
178
|
+
if (this.stopped) return;
|
|
179
|
+
const full = { service: this.service, ...event };
|
|
180
|
+
if (this.queue.length >= 1e4) return;
|
|
181
|
+
this.queue.push(full);
|
|
182
|
+
if (this.queue.length >= BATCH_SIZE) this._flush();
|
|
183
|
+
}
|
|
184
|
+
async flush(timeoutMs = 5e3) {
|
|
185
|
+
const deadline = Date.now() + timeoutMs;
|
|
186
|
+
while (this.queue.length > 0 && Date.now() < deadline) {
|
|
187
|
+
await this._flush();
|
|
188
|
+
if (this.queue.length > 0) await sleep(50);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async shutdown() {
|
|
192
|
+
this.stopped = true;
|
|
193
|
+
if (this.timer) {
|
|
194
|
+
clearInterval(this.timer);
|
|
195
|
+
this.timer = null;
|
|
196
|
+
}
|
|
197
|
+
await this.flush();
|
|
198
|
+
}
|
|
199
|
+
_flush() {
|
|
200
|
+
if (this.queue.length === 0) return Promise.resolve();
|
|
201
|
+
const batch = this.queue.splice(0, BATCH_SIZE);
|
|
202
|
+
return this._postBatch(batch);
|
|
203
|
+
}
|
|
204
|
+
async _postBatch(batch) {
|
|
205
|
+
try {
|
|
206
|
+
const url = `${this.collectorUrl}/events`;
|
|
207
|
+
await fetch(url, {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: { "Content-Type": "application/json" },
|
|
210
|
+
body: JSON.stringify({ events: batch }),
|
|
211
|
+
signal: AbortSignal.timeout(3e3)
|
|
212
|
+
});
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
function sleep(ms) {
|
|
218
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/logger.ts
|
|
222
|
+
var LEVEL_RANK = {
|
|
223
|
+
debug: 0,
|
|
224
|
+
info: 1,
|
|
225
|
+
warn: 2,
|
|
226
|
+
error: 3
|
|
227
|
+
};
|
|
228
|
+
var CONSOLE_MAP = {
|
|
229
|
+
debug: "debug",
|
|
230
|
+
info: "log",
|
|
231
|
+
warn: "warn",
|
|
232
|
+
error: "error"
|
|
233
|
+
};
|
|
234
|
+
var originals = /* @__PURE__ */ new Map();
|
|
235
|
+
function patchConsole(transport, minLevel) {
|
|
236
|
+
const levels = ["debug", "info", "warn", "error"];
|
|
237
|
+
for (const level of levels) {
|
|
238
|
+
const consoleKey = CONSOLE_MAP[level];
|
|
239
|
+
const original = console[consoleKey];
|
|
240
|
+
originals.set(level, original);
|
|
241
|
+
console[consoleKey] = (...args) => {
|
|
242
|
+
original.apply(console, args);
|
|
243
|
+
if (LEVEL_RANK[level] >= LEVEL_RANK[minLevel]) {
|
|
244
|
+
const message = args.map(
|
|
245
|
+
(a) => typeof a === "string" ? a : JSON.stringify(a)
|
|
246
|
+
).join(" ");
|
|
247
|
+
transport.send({
|
|
248
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
249
|
+
type: "log",
|
|
250
|
+
level,
|
|
251
|
+
message,
|
|
252
|
+
attributes: {}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function unpatchConsole() {
|
|
259
|
+
for (const [level, original] of originals) {
|
|
260
|
+
const consoleKey = CONSOLE_MAP[level];
|
|
261
|
+
console[consoleKey] = original;
|
|
262
|
+
}
|
|
263
|
+
originals.clear();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/span.ts
|
|
267
|
+
import { randomBytes } from "crypto";
|
|
268
|
+
var Span = class {
|
|
269
|
+
constructor(name, transport, attributes = {}) {
|
|
270
|
+
this.startTime = 0;
|
|
271
|
+
this.name = name;
|
|
272
|
+
this.spanId = randomBytes(8).toString("hex");
|
|
273
|
+
this.traceId = randomBytes(16).toString("hex");
|
|
274
|
+
this.transport = transport;
|
|
275
|
+
this.attributes = { ...attributes };
|
|
276
|
+
}
|
|
277
|
+
setAttribute(key, value) {
|
|
278
|
+
this.attributes[key] = value;
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
/** Run an async function inside this span, emit on completion. */
|
|
282
|
+
async run(fn) {
|
|
283
|
+
this.startTime = performance.now();
|
|
284
|
+
let level = "info";
|
|
285
|
+
let exceptionMsg;
|
|
286
|
+
try {
|
|
287
|
+
const result = await fn(this);
|
|
288
|
+
return result;
|
|
289
|
+
} catch (err) {
|
|
290
|
+
level = "error";
|
|
291
|
+
exceptionMsg = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
292
|
+
throw err;
|
|
293
|
+
} finally {
|
|
294
|
+
const durationMs = parseFloat(
|
|
295
|
+
(performance.now() - this.startTime).toFixed(2)
|
|
296
|
+
);
|
|
297
|
+
if (exceptionMsg) this.attributes["exception"] = exceptionMsg;
|
|
298
|
+
this.transport.send({
|
|
299
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
300
|
+
type: "span",
|
|
301
|
+
level,
|
|
302
|
+
trace_id: this.traceId,
|
|
303
|
+
span_id: this.spanId,
|
|
304
|
+
message: this.name,
|
|
305
|
+
duration_ms: durationMs,
|
|
306
|
+
attributes: this.attributes
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// src/client.ts
|
|
313
|
+
var ObservrClient = class {
|
|
314
|
+
constructor(config) {
|
|
315
|
+
this.started = false;
|
|
316
|
+
this.config = config;
|
|
317
|
+
this.service = config.service;
|
|
318
|
+
this.collectorUrl = config.collectorUrl;
|
|
319
|
+
this.transport = new Transport(config.collectorUrl, config.service);
|
|
320
|
+
}
|
|
321
|
+
start() {
|
|
322
|
+
if (this.started) return;
|
|
323
|
+
this.started = true;
|
|
324
|
+
patchConsole(this.transport, this.config.logLevel);
|
|
325
|
+
if (this.config.autoInstrument) {
|
|
326
|
+
this._autoInstrument();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/** Wrap an async function in a named span. */
|
|
330
|
+
span(name, attributes = {}) {
|
|
331
|
+
return new Span(name, this.transport, attributes);
|
|
332
|
+
}
|
|
333
|
+
async shutdown() {
|
|
334
|
+
unpatchConsole();
|
|
335
|
+
await this.transport.shutdown();
|
|
336
|
+
this.started = false;
|
|
337
|
+
}
|
|
338
|
+
_autoInstrument() {
|
|
339
|
+
if (typeof __require !== "undefined") {
|
|
340
|
+
try {
|
|
341
|
+
__require("express");
|
|
342
|
+
this._instrumentExpress();
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
__require("fastify");
|
|
347
|
+
this._instrumentFastify();
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
_instrumentExpress() {
|
|
353
|
+
try {
|
|
354
|
+
const { instrumentExpress: instrumentExpress2 } = (init_express(), __toCommonJS(express_exports));
|
|
355
|
+
instrumentExpress2(this.transport);
|
|
356
|
+
} catch {
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
_instrumentFastify() {
|
|
360
|
+
try {
|
|
361
|
+
const { instrumentFastify: instrumentFastify2 } = (init_fastify(), __toCommonJS(fastify_exports));
|
|
362
|
+
instrumentFastify2(this.transport);
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// src/index.ts
|
|
369
|
+
init_express();
|
|
370
|
+
init_fastify();
|
|
371
|
+
var _client = null;
|
|
372
|
+
function init(config = {}) {
|
|
373
|
+
_client = new ObservrClient({
|
|
374
|
+
service: config.service ?? "app",
|
|
375
|
+
collectorUrl: config.collectorUrl ?? "http://localhost:7676",
|
|
376
|
+
autoInstrument: config.autoInstrument ?? true,
|
|
377
|
+
logLevel: config.logLevel ?? "debug"
|
|
378
|
+
});
|
|
379
|
+
_client.start();
|
|
380
|
+
return _client;
|
|
381
|
+
}
|
|
382
|
+
function getClient() {
|
|
383
|
+
if (!_client) {
|
|
384
|
+
throw new Error("observr.init() must be called before getClient()");
|
|
385
|
+
}
|
|
386
|
+
return _client;
|
|
387
|
+
}
|
|
388
|
+
var observr = { init, getClient };
|
|
389
|
+
var index_default = observr;
|
|
390
|
+
export {
|
|
391
|
+
ObservrClient,
|
|
392
|
+
Span,
|
|
393
|
+
Transport,
|
|
394
|
+
index_default as default,
|
|
395
|
+
expressMiddleware,
|
|
396
|
+
fastifyPlugin,
|
|
397
|
+
getClient,
|
|
398
|
+
init
|
|
399
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ydking0911/observr",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Zero-config observability for AI agents and developers",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"lint": "tsc --noEmit",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"observability",
|
|
23
|
+
"tracing",
|
|
24
|
+
"logging",
|
|
25
|
+
"ai",
|
|
26
|
+
"devtools",
|
|
27
|
+
"apm"
|
|
28
|
+
],
|
|
29
|
+
"author": "ydking0911",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/ydking0911/observr.git",
|
|
34
|
+
"directory": "sdk/node"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/ydking0911/observr",
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^20.0.0",
|
|
39
|
+
"fastify": "^4.0.0",
|
|
40
|
+
"tsup": "^8.0.0",
|
|
41
|
+
"typescript": "^5.4.0",
|
|
42
|
+
"vitest": "^1.5.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"express": ">=4.0.0",
|
|
46
|
+
"fastify": ">=4.0.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"express": {
|
|
50
|
+
"optional": true
|
|
51
|
+
},
|
|
52
|
+
"fastify": {
|
|
53
|
+
"optional": true
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=18.0.0"
|
|
58
|
+
},
|
|
59
|
+
"files": [
|
|
60
|
+
"dist",
|
|
61
|
+
"README.md"
|
|
62
|
+
]
|
|
63
|
+
}
|