tidewave 0.3.0 → 0.4.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.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tidewave OpenTelemetry processors for Next.js
3
+ *
4
+ * Importing this module will automatically patch console.log to capture console output.
5
+ * You can then add the processors to your OpenTelemetry setup.
6
+ */
7
+ export { TidewaveSpanProcessor, TidewaveLogRecordProcessor } from '../logger/instrumentation';
@@ -0,0 +1,255 @@
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
29
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
+
31
+ // src/logger/circular-buffer.ts
32
+ class CircularBuffer {
33
+ buffer;
34
+ maxSize;
35
+ writeIndex = 0;
36
+ count = 0;
37
+ constructor(maxSize = 1024) {
38
+ this.buffer = new Array(maxSize);
39
+ this.maxSize = maxSize;
40
+ }
41
+ addLog(log) {
42
+ this.buffer[this.writeIndex] = log;
43
+ this.writeIndex = (this.writeIndex + 1) % this.maxSize;
44
+ this.count = Math.min(this.count + 1, this.maxSize);
45
+ }
46
+ getLogs(options) {
47
+ let logs = this.getAllLogs();
48
+ if (options?.level) {
49
+ const level = options.level.toUpperCase();
50
+ logs = logs.filter((log) => log.severityText === level);
51
+ }
52
+ if (options?.since) {
53
+ const sinceDate = new Date(options.since);
54
+ logs = logs.filter((log) => new Date(log.timestamp) >= sinceDate);
55
+ }
56
+ if (options?.grep) {
57
+ const regex = new RegExp(options.grep, "i");
58
+ logs = logs.filter((log) => regex.test(log.body) || regex.test(JSON.stringify(log.attributes)));
59
+ }
60
+ if (options?.tail) {
61
+ logs = logs.slice(-options.tail);
62
+ }
63
+ return logs;
64
+ }
65
+ getAllLogs() {
66
+ if (this.count < this.maxSize) {
67
+ return this.buffer.slice(0, this.count).filter(Boolean);
68
+ }
69
+ return [...this.buffer.slice(this.writeIndex), ...this.buffer.slice(0, this.writeIndex)].filter(Boolean);
70
+ }
71
+ getStats() {
72
+ return {
73
+ totalLogs: this.count,
74
+ bufferSize: this.maxSize,
75
+ bufferUsage: Math.min(this.count / this.maxSize * 100, 100).toFixed(1) + "%"
76
+ };
77
+ }
78
+ clear() {
79
+ this.buffer = new Array(this.maxSize);
80
+ this.writeIndex = 0;
81
+ this.count = 0;
82
+ }
83
+ }
84
+ var circularBuffer;
85
+ var init_circular_buffer = __esm(() => {
86
+ if (!globalThis.__tidewaveCircularBuffer) {
87
+ globalThis.__tidewaveCircularBuffer = new CircularBuffer(1024);
88
+ }
89
+ circularBuffer = globalThis.__tidewaveCircularBuffer;
90
+ });
91
+
92
+ // src/logger/tidewave-span-processor.ts
93
+ class TidewaveSpanProcessor {
94
+ onStart(_span, _parentContext) {}
95
+ onEnd(span) {
96
+ try {
97
+ const spanType = span.attributes["next.span_type"];
98
+ const httpMethod = span.attributes["http.method"];
99
+ const relevantSpanTypes = [
100
+ "BaseServer.handleRequest",
101
+ "AppRender.getBodyResult",
102
+ "AppRouteRouteHandlers.runHandler"
103
+ ];
104
+ if (!spanType || !relevantSpanTypes.includes(spanType)) {
105
+ return;
106
+ }
107
+ const route = span.attributes["next.route"] || span.attributes["http.route"];
108
+ const httpUrl = span.attributes["http.url"];
109
+ const httpTarget = span.attributes["http.target"];
110
+ const httpStatusCode = span.attributes["http.status_code"];
111
+ const path = route || httpTarget || httpUrl || "unknown";
112
+ if (typeof path === "string" && path.startsWith("/tidewave")) {
113
+ return;
114
+ }
115
+ const durationMs = this.calculateDuration(span);
116
+ let message = "";
117
+ let severity = "INFO";
118
+ if (spanType === "BaseServer.handleRequest") {
119
+ const method = httpMethod || "UNKNOWN";
120
+ const status = httpStatusCode || "unknown";
121
+ message = `${method} ${path} ${status} ${durationMs.toFixed(2)}ms`;
122
+ if (typeof httpStatusCode === "number") {
123
+ if (httpStatusCode >= 500)
124
+ severity = "ERROR";
125
+ else if (httpStatusCode >= 400)
126
+ severity = "WARN";
127
+ }
128
+ } else if (spanType === "AppRender.getBodyResult") {
129
+ message = `Rendered route: ${route || "unknown"} (${durationMs.toFixed(2)}ms)`;
130
+ } else if (spanType === "AppRouteRouteHandlers.runHandler") {
131
+ message = `API route: ${route || "unknown"} (${durationMs.toFixed(2)}ms)`;
132
+ }
133
+ circularBuffer.addLog({
134
+ timestamp: new Date(span.endTime[0] * 1000 + span.endTime[1] / 1e6).toISOString(),
135
+ severityText: severity,
136
+ body: message,
137
+ attributes: {
138
+ "log.origin": "opentelemetry-span",
139
+ "span.name": span.name,
140
+ "span.kind": span.kind,
141
+ "span.type": spanType,
142
+ "http.method": httpMethod,
143
+ "http.route": route,
144
+ "http.status_code": httpStatusCode,
145
+ "duration.ms": durationMs
146
+ }
147
+ });
148
+ } catch (_error) {}
149
+ }
150
+ calculateDuration(span) {
151
+ const startTimeMs = span.startTime[0] * 1000 + span.startTime[1] / 1e6;
152
+ const endTimeMs = span.endTime[0] * 1000 + span.endTime[1] / 1e6;
153
+ return endTimeMs - startTimeMs;
154
+ }
155
+ async forceFlush() {}
156
+ async shutdown() {}
157
+ }
158
+ var init_tidewave_span_processor = __esm(() => {
159
+ init_circular_buffer();
160
+ });
161
+
162
+ // src/logger/tidewave-log-record-processor.ts
163
+ class TidewaveLogRecordProcessor {
164
+ onEmit(logRecord, _context) {
165
+ try {
166
+ const body = String(logRecord.body || "");
167
+ circularBuffer.addLog({
168
+ timestamp: new Date(logRecord.hrTime[0] * 1000 + logRecord.hrTime[1] / 1e6).toISOString(),
169
+ severityText: logRecord.severityText || "INFO",
170
+ body,
171
+ attributes: logRecord.attributes,
172
+ resource: logRecord.resource?.attributes
173
+ });
174
+ } catch (_error) {}
175
+ }
176
+ async forceFlush() {}
177
+ async shutdown() {}
178
+ }
179
+ var init_tidewave_log_record_processor = __esm(() => {
180
+ init_circular_buffer();
181
+ });
182
+
183
+ // src/logger/instrumentation.ts
184
+ var exports_instrumentation = {};
185
+ __export(exports_instrumentation, {
186
+ TidewaveSpanProcessor: () => TidewaveSpanProcessor,
187
+ TidewaveLogRecordProcessor: () => TidewaveLogRecordProcessor
188
+ });
189
+ function stripAnsiCodes(text) {
190
+ return text.replace(ANSI_REGEX, "");
191
+ }
192
+ function patchConsole() {
193
+ const isBrowser = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
194
+ if (isBrowser) {
195
+ return;
196
+ }
197
+ if (globalThis.__TIDEWAVE_CONSOLE_PATCHED__) {
198
+ return;
199
+ }
200
+ const severityMap = {
201
+ log: "INFO",
202
+ info: "INFO",
203
+ warn: "WARN",
204
+ error: "ERROR",
205
+ debug: "DEBUG"
206
+ };
207
+ Object.entries(severityMap).forEach(([method, severity]) => {
208
+ const original = console[method].bind(console);
209
+ console[method] = (...args) => {
210
+ try {
211
+ const body = args.map((arg) => {
212
+ if (typeof arg === "string")
213
+ return arg;
214
+ if (arg instanceof Error) {
215
+ return String(arg.stack);
216
+ }
217
+ if (typeof arg === "object") {
218
+ try {
219
+ return JSON.stringify(arg);
220
+ } catch {
221
+ return String(arg);
222
+ }
223
+ }
224
+ return String(arg);
225
+ }).map(stripAnsiCodes).join(" ");
226
+ circularBuffer.addLog({
227
+ timestamp: new Date().toISOString(),
228
+ severityText: severity,
229
+ body,
230
+ attributes: {
231
+ "log.origin": "console",
232
+ "log.method": method
233
+ }
234
+ });
235
+ } catch {}
236
+ original(...args);
237
+ };
238
+ });
239
+ globalThis.__TIDEWAVE_CONSOLE_PATCHED__ = true;
240
+ }
241
+ var ANSI_REGEX;
242
+ var init_instrumentation = __esm(() => {
243
+ init_circular_buffer();
244
+ init_tidewave_span_processor();
245
+ init_tidewave_log_record_processor();
246
+ ANSI_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
247
+ patchConsole();
248
+ });
249
+
250
+ // src/next-js/instrumentation.ts
251
+ init_instrumentation();
252
+ export {
253
+ TidewaveSpanProcessor,
254
+ TidewaveLogRecordProcessor
255
+ };
package/dist/tools.d.ts CHANGED
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  export type DocsInputSchema = z.infer<typeof docsInputSchema>;
3
3
  export type SourceInputSchema = z.infer<typeof sourceInputSchema>;
4
4
  export type ProjectEvalInputSchema = z.infer<typeof projectEvalInputSchema>;
5
+ export type GetLogsInputSchema = z.infer<typeof getLogsInputSchema>;
5
6
  export interface Tool<InputSchema> {
6
7
  mcp: {
7
8
  name: string;
@@ -23,6 +24,7 @@ export interface Tools {
23
24
  docs: Tool<typeof docsInputSchema>;
24
25
  source: Tool<typeof sourceInputSchema>;
25
26
  eval: Omit<Tool<typeof projectEvalInputSchema>, 'cli'>;
27
+ logs: Omit<Tool<typeof getLogsInputSchema>, 'cli'>;
26
28
  }
27
29
  export declare const projectEvalInputSchema: z.ZodObject<{
28
30
  code: z.ZodString;
@@ -54,5 +56,21 @@ declare const sourceInputSchema: z.ZodObject<{
54
56
  }, {
55
57
  reference: string;
56
58
  }>;
59
+ export declare const getLogsInputSchema: z.ZodObject<{
60
+ tail: z.ZodDefault<z.ZodNumber>;
61
+ grep: z.ZodOptional<z.ZodString>;
62
+ level: z.ZodOptional<z.ZodEnum<["DEBUG", "INFO", "WARN", "ERROR"]>>;
63
+ since: z.ZodOptional<z.ZodString>;
64
+ }, "strip", z.ZodTypeAny, {
65
+ tail: number;
66
+ grep?: string | undefined;
67
+ level?: "DEBUG" | "INFO" | "WARN" | "ERROR" | undefined;
68
+ since?: string | undefined;
69
+ }, {
70
+ tail?: number | undefined;
71
+ grep?: string | undefined;
72
+ level?: "DEBUG" | "INFO" | "WARN" | "ERROR" | undefined;
73
+ since?: string | undefined;
74
+ }>;
57
75
  export declare const tools: Tools;
58
76
  export {};
@@ -1,3 +1,4 @@
1
1
  import type { TidewaveConfig } from './core';
2
2
  import type { Plugin } from 'vite';
3
+ import './logger/instrumentation';
3
4
  export default function tidewave(config?: TidewaveConfig): Plugin;