evlog 1.5.0 → 1.7.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.
Files changed (102) hide show
  1. package/README.md +112 -0
  2. package/dist/_utils-DZA9nou3.mjs +23 -0
  3. package/dist/_utils-DZA9nou3.mjs.map +1 -0
  4. package/dist/adapters/axiom.d.mts +16 -15
  5. package/dist/adapters/axiom.d.mts.map +1 -0
  6. package/dist/adapters/axiom.mjs +96 -57
  7. package/dist/adapters/axiom.mjs.map +1 -0
  8. package/dist/adapters/better-stack.d.mts +62 -0
  9. package/dist/adapters/better-stack.d.mts.map +1 -0
  10. package/dist/adapters/better-stack.mjs +109 -0
  11. package/dist/adapters/better-stack.mjs.map +1 -0
  12. package/dist/adapters/otlp.d.mts +31 -30
  13. package/dist/adapters/otlp.d.mts.map +1 -0
  14. package/dist/adapters/otlp.mjs +198 -184
  15. package/dist/adapters/otlp.mjs.map +1 -0
  16. package/dist/adapters/posthog.d.mts +20 -19
  17. package/dist/adapters/posthog.d.mts.map +1 -0
  18. package/dist/adapters/posthog.mjs +110 -69
  19. package/dist/adapters/posthog.mjs.map +1 -0
  20. package/dist/adapters/sentry.d.mts +79 -0
  21. package/dist/adapters/sentry.d.mts.map +1 -0
  22. package/dist/adapters/sentry.mjs +233 -0
  23. package/dist/adapters/sentry.mjs.map +1 -0
  24. package/dist/enrichers.d.mts +74 -0
  25. package/dist/enrichers.d.mts.map +1 -0
  26. package/dist/enrichers.mjs +172 -0
  27. package/dist/enrichers.mjs.map +1 -0
  28. package/dist/error.d.mts +24 -22
  29. package/dist/error.d.mts.map +1 -0
  30. package/dist/error.mjs +107 -76
  31. package/dist/error.mjs.map +1 -0
  32. package/dist/index.d.mts +6 -5
  33. package/dist/index.mjs +6 -6
  34. package/dist/logger.d.mts +5 -3
  35. package/dist/logger.d.mts.map +1 -0
  36. package/dist/logger.mjs +204 -173
  37. package/dist/logger.mjs.map +1 -0
  38. package/dist/nitro/errorHandler.d.mts +4 -2
  39. package/dist/nitro/errorHandler.d.mts.map +1 -0
  40. package/dist/nitro/errorHandler.mjs +49 -38
  41. package/dist/nitro/errorHandler.mjs.map +1 -0
  42. package/dist/nitro/plugin.d.mts +4 -2
  43. package/dist/nitro/plugin.d.mts.map +1 -0
  44. package/dist/nitro/plugin.mjs +155 -137
  45. package/dist/nitro/plugin.mjs.map +1 -0
  46. package/dist/nuxt/module.d.mts +149 -122
  47. package/dist/nuxt/module.d.mts.map +1 -0
  48. package/dist/nuxt/module.mjs +66 -65
  49. package/dist/nuxt/module.mjs.map +1 -0
  50. package/dist/pipeline.d.mts +46 -0
  51. package/dist/pipeline.d.mts.map +1 -0
  52. package/dist/pipeline.mjs +122 -0
  53. package/dist/pipeline.mjs.map +1 -0
  54. package/dist/runtime/client/log.d.mts +7 -5
  55. package/dist/runtime/client/log.d.mts.map +1 -0
  56. package/dist/runtime/client/log.mjs +57 -62
  57. package/dist/runtime/client/log.mjs.map +1 -0
  58. package/dist/runtime/client/plugin.d.mts +3 -1
  59. package/dist/runtime/client/plugin.d.mts.map +1 -0
  60. package/dist/runtime/client/plugin.mjs +13 -12
  61. package/dist/runtime/client/plugin.mjs.map +1 -0
  62. package/dist/runtime/server/routes/_evlog/ingest.post.d.mts +4 -2
  63. package/dist/runtime/server/routes/_evlog/ingest.post.d.mts.map +1 -0
  64. package/dist/runtime/server/routes/_evlog/ingest.post.mjs +113 -77
  65. package/dist/runtime/server/routes/_evlog/ingest.post.mjs.map +1 -0
  66. package/dist/runtime/server/useLogger.d.mts +14 -3
  67. package/dist/runtime/server/useLogger.d.mts.map +1 -0
  68. package/dist/runtime/server/useLogger.mjs +39 -10
  69. package/dist/runtime/server/useLogger.mjs.map +1 -0
  70. package/dist/runtime/utils/parseError.d.mts +5 -3
  71. package/dist/runtime/utils/parseError.d.mts.map +1 -0
  72. package/dist/runtime/utils/parseError.mjs +25 -26
  73. package/dist/runtime/utils/parseError.mjs.map +1 -0
  74. package/dist/types.d.mts +348 -246
  75. package/dist/types.d.mts.map +1 -0
  76. package/dist/types.mjs +1 -1
  77. package/dist/utils.d.mts +19 -14
  78. package/dist/utils.d.mts.map +1 -0
  79. package/dist/utils.mjs +59 -50
  80. package/dist/utils.mjs.map +1 -0
  81. package/dist/workers.d.mts +10 -9
  82. package/dist/workers.d.mts.map +1 -0
  83. package/dist/workers.mjs +68 -40
  84. package/dist/workers.mjs.map +1 -0
  85. package/package.json +37 -9
  86. package/dist/adapters/axiom.d.ts +0 -62
  87. package/dist/adapters/otlp.d.ts +0 -83
  88. package/dist/adapters/posthog.d.ts +0 -72
  89. package/dist/error.d.ts +0 -63
  90. package/dist/index.d.ts +0 -5
  91. package/dist/logger.d.ts +0 -40
  92. package/dist/nitro/errorHandler.d.ts +0 -13
  93. package/dist/nitro/plugin.d.ts +0 -5
  94. package/dist/nuxt/module.d.ts +0 -125
  95. package/dist/runtime/client/log.d.ts +0 -10
  96. package/dist/runtime/client/plugin.d.ts +0 -3
  97. package/dist/runtime/server/routes/_evlog/ingest.post.d.ts +0 -5
  98. package/dist/runtime/server/useLogger.d.ts +0 -28
  99. package/dist/runtime/utils/parseError.d.ts +0 -5
  100. package/dist/types.d.ts +0 -364
  101. package/dist/utils.d.ts +0 -29
  102. package/dist/workers.d.ts +0 -45
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otlp.d.mts","names":[],"sources":["../../src/adapters/otlp.ts"],"mappings":";;;UAGiB,UAAA;;EAEf,QAAA;EAFyB;EAIzB,WAAA;EAIgB;EAFhB,kBAAA,GAAqB,MAAA;EAFrB;EAIA,OAAA,GAAU,MAAA;EAFW;EAIrB,OAAA;AAAA;;UAIe,aAAA;EACf,YAAA;EACA,cAAA;EACA,YAAA;EACA,IAAA;IAAQ,WAAA;EAAA;EACR,UAAA,EAAY,KAAA;IACV,GAAA;IACA,KAAA;MAAS,WAAA;MAAsB,QAAA;MAAmB,SAAA;IAAA;EAAA;EAEpD,OAAA;EACA,MAAA;AAAA;;;;iBAkEc,eAAA,CAAgB,KAAA,EAAO,SAAA,GAAY,aAAA;AAAnD;;;;;;;;;AA4HA;;;;;;;;;;;AA5HA,iBA4HgB,eAAA,CAAgB,SAAA,GAAY,OAAA,CAAQ,UAAA,KAAe,GAAA,EAAK,YAAA,GAAe,YAAA,OAAmB,OAAA;;;;;;;;AA0E1G;;;iBAAsB,UAAA,CAAW,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,UAAA,GAAa,OAAA;;;;;;;;;;;iBAclD,eAAA,CAAgB,MAAA,EAAQ,SAAA,IAAa,MAAA,EAAQ,UAAA,GAAa,OAAA"}
@@ -1,202 +1,216 @@
1
+ import { t as getRuntimeConfig } from "../_utils-DZA9nou3.mjs";
2
+
3
+ //#region src/adapters/otlp.ts
4
+ /**
5
+ * Map evlog levels to OTLP severity numbers.
6
+ * Based on OpenTelemetry Logs Data Model specification.
7
+ */
1
8
  const SEVERITY_MAP = {
2
- debug: 5,
3
- // DEBUG
4
- info: 9,
5
- // INFO
6
- warn: 13,
7
- // WARN
8
- error: 17
9
- // ERROR
9
+ debug: 5,
10
+ info: 9,
11
+ warn: 13,
12
+ error: 17
10
13
  };
11
14
  const SEVERITY_TEXT_MAP = {
12
- debug: "DEBUG",
13
- info: "INFO",
14
- warn: "WARN",
15
- error: "ERROR"
15
+ debug: "DEBUG",
16
+ info: "INFO",
17
+ warn: "WARN",
18
+ error: "ERROR"
16
19
  };
17
- function getRuntimeConfig() {
18
- try {
19
- const { useRuntimeConfig } = require("nitropack/runtime");
20
- return useRuntimeConfig();
21
- } catch {
22
- return void 0;
23
- }
24
- }
20
+ /**
21
+ * Convert a value to OTLP attribute value format.
22
+ */
25
23
  function toAttributeValue(value) {
26
- if (typeof value === "boolean") {
27
- return { boolValue: value };
28
- }
29
- if (typeof value === "number" && Number.isInteger(value)) {
30
- return { intValue: String(value) };
31
- }
32
- if (typeof value === "string") {
33
- return { stringValue: value };
34
- }
35
- return { stringValue: JSON.stringify(value) };
24
+ if (typeof value === "boolean") return { boolValue: value };
25
+ if (typeof value === "number" && Number.isInteger(value)) return { intValue: String(value) };
26
+ if (typeof value === "string") return { stringValue: value };
27
+ return { stringValue: JSON.stringify(value) };
36
28
  }
29
+ /**
30
+ * Convert an evlog WideEvent to an OTLP LogRecord.
31
+ */
37
32
  function toOTLPLogRecord(event) {
38
- const timestamp = new Date(event.timestamp).getTime() * 1e6;
39
- const { level, traceId, spanId, ...rest } = event;
40
- delete rest.timestamp;
41
- delete rest.service;
42
- delete rest.environment;
43
- delete rest.version;
44
- delete rest.commitHash;
45
- delete rest.region;
46
- const attributes = [];
47
- for (const [key, value] of Object.entries(rest)) {
48
- if (value !== void 0 && value !== null) {
49
- attributes.push({
50
- key,
51
- value: toAttributeValue(value)
52
- });
53
- }
54
- }
55
- const record = {
56
- timeUnixNano: String(timestamp),
57
- severityNumber: SEVERITY_MAP[level] ?? 9,
58
- severityText: SEVERITY_TEXT_MAP[level] ?? "INFO",
59
- body: { stringValue: JSON.stringify(event) },
60
- attributes
61
- };
62
- if (typeof traceId === "string") {
63
- record.traceId = traceId;
64
- }
65
- if (typeof spanId === "string") {
66
- record.spanId = spanId;
67
- }
68
- return record;
33
+ const timestamp = new Date(event.timestamp).getTime() * 1e6;
34
+ const { level, traceId, spanId, ...rest } = event;
35
+ delete rest.timestamp;
36
+ delete rest.service;
37
+ delete rest.environment;
38
+ delete rest.version;
39
+ delete rest.commitHash;
40
+ delete rest.region;
41
+ const attributes = [];
42
+ for (const [key, value] of Object.entries(rest)) if (value !== void 0 && value !== null) attributes.push({
43
+ key,
44
+ value: toAttributeValue(value)
45
+ });
46
+ const record = {
47
+ timeUnixNano: String(timestamp),
48
+ severityNumber: SEVERITY_MAP[level] ?? 9,
49
+ severityText: SEVERITY_TEXT_MAP[level] ?? "INFO",
50
+ body: { stringValue: JSON.stringify(event) },
51
+ attributes
52
+ };
53
+ if (typeof traceId === "string") record.traceId = traceId;
54
+ if (typeof spanId === "string") record.spanId = spanId;
55
+ return record;
69
56
  }
57
+ /**
58
+ * Build OTLP resource attributes from event and config.
59
+ */
70
60
  function buildResourceAttributes(event, config) {
71
- const attributes = [];
72
- attributes.push({
73
- key: "service.name",
74
- value: { stringValue: config.serviceName ?? event.service }
75
- });
76
- if (event.environment) {
77
- attributes.push({
78
- key: "deployment.environment",
79
- value: { stringValue: event.environment }
80
- });
81
- }
82
- if (event.version) {
83
- attributes.push({
84
- key: "service.version",
85
- value: { stringValue: event.version }
86
- });
87
- }
88
- if (event.region) {
89
- attributes.push({
90
- key: "cloud.region",
91
- value: { stringValue: event.region }
92
- });
93
- }
94
- if (event.commitHash) {
95
- attributes.push({
96
- key: "vcs.commit.id",
97
- value: { stringValue: event.commitHash }
98
- });
99
- }
100
- if (config.resourceAttributes) {
101
- for (const [key, value] of Object.entries(config.resourceAttributes)) {
102
- attributes.push({
103
- key,
104
- value: toAttributeValue(value)
105
- });
106
- }
107
- }
108
- return attributes;
61
+ const attributes = [];
62
+ attributes.push({
63
+ key: "service.name",
64
+ value: { stringValue: config.serviceName ?? event.service }
65
+ });
66
+ if (event.environment) attributes.push({
67
+ key: "deployment.environment",
68
+ value: { stringValue: event.environment }
69
+ });
70
+ if (event.version) attributes.push({
71
+ key: "service.version",
72
+ value: { stringValue: event.version }
73
+ });
74
+ if (event.region) attributes.push({
75
+ key: "cloud.region",
76
+ value: { stringValue: event.region }
77
+ });
78
+ if (event.commitHash) attributes.push({
79
+ key: "vcs.commit.id",
80
+ value: { stringValue: event.commitHash }
81
+ });
82
+ if (config.resourceAttributes) for (const [key, value] of Object.entries(config.resourceAttributes)) attributes.push({
83
+ key,
84
+ value: toAttributeValue(value)
85
+ });
86
+ return attributes;
109
87
  }
88
+ /**
89
+ * Create a drain function for sending logs to an OTLP endpoint.
90
+ *
91
+ * Configuration priority (highest to lowest):
92
+ * 1. Overrides passed to createOTLPDrain()
93
+ * 2. runtimeConfig.evlog.otlp (NUXT_EVLOG_OTLP_*)
94
+ * 3. runtimeConfig.otlp (NUXT_OTLP_*)
95
+ * 4. Environment variables: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * // Zero config - reads from runtimeConfig or env vars
100
+ * nitroApp.hooks.hook('evlog:drain', createOTLPDrain())
101
+ *
102
+ * // With overrides
103
+ * nitroApp.hooks.hook('evlog:drain', createOTLPDrain({
104
+ * endpoint: 'http://localhost:4318',
105
+ * }))
106
+ * ```
107
+ */
110
108
  function createOTLPDrain(overrides) {
111
- return async (ctx) => {
112
- const runtimeConfig = getRuntimeConfig();
113
- const evlogOtlp = runtimeConfig?.evlog?.otlp;
114
- const rootOtlp = runtimeConfig?.otlp;
115
- const getHeadersFromEnv = () => {
116
- const headersEnv = process.env.OTEL_EXPORTER_OTLP_HEADERS || process.env.NUXT_OTLP_HEADERS;
117
- if (headersEnv) {
118
- const headers = {};
119
- const decoded = decodeURIComponent(headersEnv);
120
- for (const pair of decoded.split(",")) {
121
- const eqIndex = pair.indexOf("=");
122
- if (eqIndex > 0) {
123
- const key = pair.slice(0, eqIndex).trim();
124
- const value = pair.slice(eqIndex + 1).trim();
125
- if (key && value) {
126
- headers[key] = value;
127
- }
128
- }
129
- }
130
- if (Object.keys(headers).length > 0) return headers;
131
- }
132
- const auth = process.env.NUXT_OTLP_AUTH;
133
- if (auth) {
134
- return { Authorization: auth };
135
- }
136
- return void 0;
137
- };
138
- const config = {
139
- endpoint: overrides?.endpoint ?? evlogOtlp?.endpoint ?? rootOtlp?.endpoint ?? process.env.NUXT_OTLP_ENDPOINT ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
140
- serviceName: overrides?.serviceName ?? evlogOtlp?.serviceName ?? rootOtlp?.serviceName ?? process.env.NUXT_OTLP_SERVICE_NAME ?? process.env.OTEL_SERVICE_NAME,
141
- headers: overrides?.headers ?? evlogOtlp?.headers ?? rootOtlp?.headers ?? getHeadersFromEnv(),
142
- resourceAttributes: overrides?.resourceAttributes ?? evlogOtlp?.resourceAttributes ?? rootOtlp?.resourceAttributes,
143
- timeout: overrides?.timeout ?? evlogOtlp?.timeout ?? rootOtlp?.timeout
144
- };
145
- if (!config.endpoint) {
146
- console.error("[evlog/otlp] Missing endpoint. Set NUXT_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT env var, or pass to createOTLPDrain()");
147
- return;
148
- }
149
- try {
150
- await sendToOTLP(ctx.event, config);
151
- } catch (error) {
152
- console.error("[evlog/otlp] Failed to send event:", error);
153
- }
154
- };
109
+ return async (ctx) => {
110
+ const contexts = Array.isArray(ctx) ? ctx : [ctx];
111
+ if (contexts.length === 0) return;
112
+ const runtimeConfig = getRuntimeConfig();
113
+ const evlogOtlp = runtimeConfig?.evlog?.otlp;
114
+ const rootOtlp = runtimeConfig?.otlp;
115
+ const getHeadersFromEnv = () => {
116
+ const headersEnv = process.env.OTEL_EXPORTER_OTLP_HEADERS || process.env.NUXT_OTLP_HEADERS;
117
+ if (headersEnv) {
118
+ const headers = {};
119
+ const decoded = decodeURIComponent(headersEnv);
120
+ for (const pair of decoded.split(",")) {
121
+ const eqIndex = pair.indexOf("=");
122
+ if (eqIndex > 0) {
123
+ const key = pair.slice(0, eqIndex).trim();
124
+ const value = pair.slice(eqIndex + 1).trim();
125
+ if (key && value) headers[key] = value;
126
+ }
127
+ }
128
+ if (Object.keys(headers).length > 0) return headers;
129
+ }
130
+ const auth = process.env.NUXT_OTLP_AUTH;
131
+ if (auth) return { Authorization: auth };
132
+ };
133
+ const config = {
134
+ endpoint: overrides?.endpoint ?? evlogOtlp?.endpoint ?? rootOtlp?.endpoint ?? process.env.NUXT_OTLP_ENDPOINT ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
135
+ serviceName: overrides?.serviceName ?? evlogOtlp?.serviceName ?? rootOtlp?.serviceName ?? process.env.NUXT_OTLP_SERVICE_NAME ?? process.env.OTEL_SERVICE_NAME,
136
+ headers: overrides?.headers ?? evlogOtlp?.headers ?? rootOtlp?.headers ?? getHeadersFromEnv(),
137
+ resourceAttributes: overrides?.resourceAttributes ?? evlogOtlp?.resourceAttributes ?? rootOtlp?.resourceAttributes,
138
+ timeout: overrides?.timeout ?? evlogOtlp?.timeout ?? rootOtlp?.timeout
139
+ };
140
+ if (!config.endpoint) {
141
+ console.error("[evlog/otlp] Missing endpoint. Set NUXT_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT env var, or pass to createOTLPDrain()");
142
+ return;
143
+ }
144
+ try {
145
+ await sendBatchToOTLP(contexts.map((c) => c.event), config);
146
+ } catch (error) {
147
+ console.error("[evlog/otlp] Failed to send events to OTLP:", error);
148
+ }
149
+ };
155
150
  }
151
+ /**
152
+ * Send a single event to an OTLP endpoint.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * await sendToOTLP(event, {
157
+ * endpoint: 'http://localhost:4318',
158
+ * })
159
+ * ```
160
+ */
156
161
  async function sendToOTLP(event, config) {
157
- await sendBatchToOTLP([event], config);
162
+ await sendBatchToOTLP([event], config);
158
163
  }
164
+ /**
165
+ * Send a batch of events to an OTLP endpoint.
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * await sendBatchToOTLP(events, {
170
+ * endpoint: 'http://localhost:4318',
171
+ * })
172
+ * ```
173
+ */
159
174
  async function sendBatchToOTLP(events, config) {
160
- if (events.length === 0) return;
161
- const timeout = config.timeout ?? 5e3;
162
- const url = `${config.endpoint.replace(/\/$/, "")}/v1/logs`;
163
- const [firstEvent] = events;
164
- const resourceAttributes = buildResourceAttributes(firstEvent, config);
165
- const logRecords = events.map(toOTLPLogRecord);
166
- const payload = {
167
- resourceLogs: [
168
- {
169
- resource: { attributes: resourceAttributes },
170
- scopeLogs: [
171
- {
172
- scope: { name: "evlog", version: "1.0.0" },
173
- logRecords
174
- }
175
- ]
176
- }
177
- ]
178
- };
179
- const headers = {
180
- "Content-Type": "application/json",
181
- ...config.headers
182
- };
183
- const controller = new AbortController();
184
- const timeoutId = setTimeout(() => controller.abort(), timeout);
185
- try {
186
- const response = await fetch(url, {
187
- method: "POST",
188
- headers,
189
- body: JSON.stringify(payload),
190
- signal: controller.signal
191
- });
192
- if (!response.ok) {
193
- const text = await response.text().catch(() => "Unknown error");
194
- const safeText = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text;
195
- throw new Error(`OTLP API error: ${response.status} ${response.statusText} - ${safeText}`);
196
- }
197
- } finally {
198
- clearTimeout(timeoutId);
199
- }
175
+ if (events.length === 0) return;
176
+ const timeout = config.timeout ?? 5e3;
177
+ const url = `${config.endpoint.replace(/\/$/, "")}/v1/logs`;
178
+ const [firstEvent] = events;
179
+ const resourceAttributes = buildResourceAttributes(firstEvent, config);
180
+ const logRecords = events.map(toOTLPLogRecord);
181
+ const payload = { resourceLogs: [{
182
+ resource: { attributes: resourceAttributes },
183
+ scopeLogs: [{
184
+ scope: {
185
+ name: "evlog",
186
+ version: "1.0.0"
187
+ },
188
+ logRecords
189
+ }]
190
+ }] };
191
+ const headers = {
192
+ "Content-Type": "application/json",
193
+ ...config.headers
194
+ };
195
+ const controller = new AbortController();
196
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
197
+ try {
198
+ const response = await fetch(url, {
199
+ method: "POST",
200
+ headers,
201
+ body: JSON.stringify(payload),
202
+ signal: controller.signal
203
+ });
204
+ if (!response.ok) {
205
+ const text = await response.text().catch(() => "Unknown error");
206
+ const safeText = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text;
207
+ throw new Error(`OTLP API error: ${response.status} ${response.statusText} - ${safeText}`);
208
+ }
209
+ } finally {
210
+ clearTimeout(timeoutId);
211
+ }
200
212
  }
201
213
 
214
+ //#endregion
202
215
  export { createOTLPDrain, sendBatchToOTLP, sendToOTLP, toOTLPLogRecord };
216
+ //# sourceMappingURL=otlp.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otlp.mjs","names":[],"sources":["../../src/adapters/otlp.ts"],"sourcesContent":["import type { DrainContext, LogLevel, WideEvent } from '../types'\nimport { getRuntimeConfig } from './_utils'\n\nexport interface OTLPConfig {\n /** OTLP HTTP endpoint (e.g., http://localhost:4318) */\n endpoint: string\n /** Override service name (defaults to event.service) */\n serviceName?: string\n /** Additional resource attributes */\n resourceAttributes?: Record<string, string | number | boolean>\n /** Custom headers (e.g., for authentication) */\n headers?: Record<string, string>\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n}\n\n/** OTLP Log Record structure */\nexport interface OTLPLogRecord {\n timeUnixNano: string\n severityNumber: number\n severityText: string\n body: { stringValue: string }\n attributes: Array<{\n key: string\n value: { stringValue?: string, intValue?: string, boolValue?: boolean }\n }>\n traceId?: string\n spanId?: string\n}\n\n/** OTLP Resource structure */\ninterface OTLPResource {\n attributes: Array<{\n key: string\n value: { stringValue?: string, intValue?: string, boolValue?: boolean }\n }>\n}\n\n/** OTLP Scope structure */\ninterface OTLPScope {\n name: string\n version?: string\n}\n\n/** OTLP ExportLogsServiceRequest structure */\ninterface ExportLogsServiceRequest {\n resourceLogs: Array<{\n resource: OTLPResource\n scopeLogs: Array<{\n scope: OTLPScope\n logRecords: OTLPLogRecord[]\n }>\n }>\n}\n\n/**\n * Map evlog levels to OTLP severity numbers.\n * Based on OpenTelemetry Logs Data Model specification.\n */\nconst SEVERITY_MAP: Record<LogLevel, number> = {\n debug: 5, // DEBUG\n info: 9, // INFO\n warn: 13, // WARN\n error: 17, // ERROR\n}\n\nconst SEVERITY_TEXT_MAP: Record<LogLevel, string> = {\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n}\n\n/**\n * Convert a value to OTLP attribute value format.\n */\nfunction toAttributeValue(value: unknown): { stringValue?: string, intValue?: string, boolValue?: boolean } {\n if (typeof value === 'boolean') {\n return { boolValue: value }\n }\n if (typeof value === 'number' && Number.isInteger(value)) {\n return { intValue: String(value) }\n }\n if (typeof value === 'string') {\n return { stringValue: value }\n }\n // For complex types, serialize to JSON string\n return { stringValue: JSON.stringify(value) }\n}\n\n/**\n * Convert an evlog WideEvent to an OTLP LogRecord.\n */\nexport function toOTLPLogRecord(event: WideEvent): OTLPLogRecord {\n const timestamp = new Date(event.timestamp).getTime() * 1_000_000 // Convert to nanoseconds\n\n // Extract known fields, rest goes to attributes\n const { level, traceId, spanId, ...rest } = event\n // Remove base fields from rest (they're handled as resource attributes)\n delete (rest as Record<string, unknown>).timestamp\n delete (rest as Record<string, unknown>).service\n delete (rest as Record<string, unknown>).environment\n delete (rest as Record<string, unknown>).version\n delete (rest as Record<string, unknown>).commitHash\n delete (rest as Record<string, unknown>).region\n\n const attributes: OTLPLogRecord['attributes'] = []\n\n // Add all remaining event fields as attributes\n for (const [key, value] of Object.entries(rest)) {\n if (value !== undefined && value !== null) {\n attributes.push({\n key,\n value: toAttributeValue(value),\n })\n }\n }\n\n const record: OTLPLogRecord = {\n timeUnixNano: String(timestamp),\n severityNumber: SEVERITY_MAP[level] ?? 9,\n severityText: SEVERITY_TEXT_MAP[level] ?? 'INFO',\n body: { stringValue: JSON.stringify(event) },\n attributes,\n }\n\n // Add trace context if present\n if (typeof traceId === 'string') {\n record.traceId = traceId\n }\n if (typeof spanId === 'string') {\n record.spanId = spanId\n }\n\n return record\n}\n\n/**\n * Build OTLP resource attributes from event and config.\n */\nfunction buildResourceAttributes(\n event: WideEvent,\n config: OTLPConfig,\n): OTLPResource['attributes'] {\n const attributes: OTLPResource['attributes'] = []\n\n // Service name\n attributes.push({\n key: 'service.name',\n value: { stringValue: config.serviceName ?? event.service },\n })\n\n // Environment\n if (event.environment) {\n attributes.push({\n key: 'deployment.environment',\n value: { stringValue: event.environment },\n })\n }\n\n // Version\n if (event.version) {\n attributes.push({\n key: 'service.version',\n value: { stringValue: event.version },\n })\n }\n\n // Region\n if (event.region) {\n attributes.push({\n key: 'cloud.region',\n value: { stringValue: event.region },\n })\n }\n\n // Commit hash\n if (event.commitHash) {\n attributes.push({\n key: 'vcs.commit.id',\n value: { stringValue: event.commitHash },\n })\n }\n\n // Custom resource attributes from config\n if (config.resourceAttributes) {\n for (const [key, value] of Object.entries(config.resourceAttributes)) {\n attributes.push({\n key,\n value: toAttributeValue(value),\n })\n }\n }\n\n return attributes\n}\n\n/**\n * Create a drain function for sending logs to an OTLP endpoint.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createOTLPDrain()\n * 2. runtimeConfig.evlog.otlp (NUXT_EVLOG_OTLP_*)\n * 3. runtimeConfig.otlp (NUXT_OTLP_*)\n * 4. Environment variables: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME\n *\n * @example\n * ```ts\n * // Zero config - reads from runtimeConfig or env vars\n * nitroApp.hooks.hook('evlog:drain', createOTLPDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createOTLPDrain({\n * endpoint: 'http://localhost:4318',\n * }))\n * ```\n */\nexport function createOTLPDrain(overrides?: Partial<OTLPConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void> {\n return async (ctx: DrainContext | DrainContext[]) => {\n const contexts = Array.isArray(ctx) ? ctx : [ctx]\n if (contexts.length === 0) return\n\n const runtimeConfig = getRuntimeConfig()\n // Support both runtimeConfig.evlog.otlp and runtimeConfig.otlp\n const evlogOtlp = runtimeConfig?.evlog?.otlp\n const rootOtlp = runtimeConfig?.otlp\n\n // Build headers from env vars (supports multiple formats)\n const getHeadersFromEnv = (): Record<string, string> | undefined => {\n // OTEL standard: OTEL_EXPORTER_OTLP_HEADERS=Authorization=Basic xxx\n // or Grafana format: Authorization=Basic%20xxx\n const headersEnv = process.env.OTEL_EXPORTER_OTLP_HEADERS || process.env.NUXT_OTLP_HEADERS\n if (headersEnv) {\n const headers: Record<string, string> = {}\n // Decode URL encoding if present\n const decoded = decodeURIComponent(headersEnv)\n // Parse key=value pairs (comma-separated)\n for (const pair of decoded.split(',')) {\n const eqIndex = pair.indexOf('=')\n if (eqIndex > 0) {\n const key = pair.slice(0, eqIndex).trim()\n const value = pair.slice(eqIndex + 1).trim()\n if (key && value) {\n headers[key] = value\n }\n }\n }\n if (Object.keys(headers).length > 0) return headers\n }\n\n // Simple format: NUXT_OTLP_AUTH=Basic xxx → Authorization: Basic xxx\n const auth = process.env.NUXT_OTLP_AUTH\n if (auth) {\n return { Authorization: auth }\n }\n\n return undefined\n }\n\n // Build config with fallbacks: overrides > evlog.otlp > otlp > env vars (NUXT_OTLP_* or OTEL_*)\n const config: Partial<OTLPConfig> = {\n endpoint: overrides?.endpoint ?? evlogOtlp?.endpoint ?? rootOtlp?.endpoint ?? process.env.NUXT_OTLP_ENDPOINT ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT,\n serviceName: overrides?.serviceName ?? evlogOtlp?.serviceName ?? rootOtlp?.serviceName ?? process.env.NUXT_OTLP_SERVICE_NAME ?? process.env.OTEL_SERVICE_NAME,\n headers: overrides?.headers ?? evlogOtlp?.headers ?? rootOtlp?.headers ?? getHeadersFromEnv(),\n resourceAttributes: overrides?.resourceAttributes ?? evlogOtlp?.resourceAttributes ?? rootOtlp?.resourceAttributes,\n timeout: overrides?.timeout ?? evlogOtlp?.timeout ?? rootOtlp?.timeout,\n }\n\n if (!config.endpoint) {\n console.error('[evlog/otlp] Missing endpoint. Set NUXT_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT env var, or pass to createOTLPDrain()')\n return\n }\n\n try {\n await sendBatchToOTLP(contexts.map(c => c.event), config as OTLPConfig)\n } catch (error) {\n console.error('[evlog/otlp] Failed to send events to OTLP:', error)\n }\n }\n}\n\n/**\n * Send a single event to an OTLP endpoint.\n *\n * @example\n * ```ts\n * await sendToOTLP(event, {\n * endpoint: 'http://localhost:4318',\n * })\n * ```\n */\nexport async function sendToOTLP(event: WideEvent, config: OTLPConfig): Promise<void> {\n await sendBatchToOTLP([event], config)\n}\n\n/**\n * Send a batch of events to an OTLP endpoint.\n *\n * @example\n * ```ts\n * await sendBatchToOTLP(events, {\n * endpoint: 'http://localhost:4318',\n * })\n * ```\n */\nexport async function sendBatchToOTLP(events: WideEvent[], config: OTLPConfig): Promise<void> {\n if (events.length === 0) return\n\n const timeout = config.timeout ?? 5000\n const url = `${config.endpoint.replace(/\\/$/, '')}/v1/logs`\n\n // Group events by service for proper resource attribution\n // For simplicity, we use the first event's resource attributes\n const [firstEvent] = events\n const resourceAttributes = buildResourceAttributes(firstEvent, config)\n\n const logRecords = events.map(toOTLPLogRecord)\n\n const payload: ExportLogsServiceRequest = {\n resourceLogs: [\n {\n resource: { attributes: resourceAttributes },\n scopeLogs: [\n {\n scope: { name: 'evlog', version: '1.0.0' },\n logRecords,\n },\n ],\n },\n ],\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...config.headers,\n }\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(payload),\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const text = await response.text().catch(() => 'Unknown error')\n const safeText = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text\n throw new Error(`OTLP API error: ${response.status} ${response.statusText} - ${safeText}`)\n }\n } finally {\n clearTimeout(timeoutId)\n }\n}\n"],"mappings":";;;;;;;AA2DA,MAAM,eAAyC;CAC7C,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR;AAED,MAAM,oBAA8C;CAClD,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR;;;;AAKD,SAAS,iBAAiB,OAAkF;AAC1G,KAAI,OAAO,UAAU,UACnB,QAAO,EAAE,WAAW,OAAO;AAE7B,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,MAAM,CACtD,QAAO,EAAE,UAAU,OAAO,MAAM,EAAE;AAEpC,KAAI,OAAO,UAAU,SACnB,QAAO,EAAE,aAAa,OAAO;AAG/B,QAAO,EAAE,aAAa,KAAK,UAAU,MAAM,EAAE;;;;;AAM/C,SAAgB,gBAAgB,OAAiC;CAC/D,MAAM,YAAY,IAAI,KAAK,MAAM,UAAU,CAAC,SAAS,GAAG;CAGxD,MAAM,EAAE,OAAO,SAAS,QAAQ,GAAG,SAAS;AAE5C,QAAQ,KAAiC;AACzC,QAAQ,KAAiC;AACzC,QAAQ,KAAiC;AACzC,QAAQ,KAAiC;AACzC,QAAQ,KAAiC;AACzC,QAAQ,KAAiC;CAEzC,MAAM,aAA0C,EAAE;AAGlD,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,CAC7C,KAAI,UAAU,UAAa,UAAU,KACnC,YAAW,KAAK;EACd;EACA,OAAO,iBAAiB,MAAM;EAC/B,CAAC;CAIN,MAAM,SAAwB;EAC5B,cAAc,OAAO,UAAU;EAC/B,gBAAgB,aAAa,UAAU;EACvC,cAAc,kBAAkB,UAAU;EAC1C,MAAM,EAAE,aAAa,KAAK,UAAU,MAAM,EAAE;EAC5C;EACD;AAGD,KAAI,OAAO,YAAY,SACrB,QAAO,UAAU;AAEnB,KAAI,OAAO,WAAW,SACpB,QAAO,SAAS;AAGlB,QAAO;;;;;AAMT,SAAS,wBACP,OACA,QAC4B;CAC5B,MAAM,aAAyC,EAAE;AAGjD,YAAW,KAAK;EACd,KAAK;EACL,OAAO,EAAE,aAAa,OAAO,eAAe,MAAM,SAAS;EAC5D,CAAC;AAGF,KAAI,MAAM,YACR,YAAW,KAAK;EACd,KAAK;EACL,OAAO,EAAE,aAAa,MAAM,aAAa;EAC1C,CAAC;AAIJ,KAAI,MAAM,QACR,YAAW,KAAK;EACd,KAAK;EACL,OAAO,EAAE,aAAa,MAAM,SAAS;EACtC,CAAC;AAIJ,KAAI,MAAM,OACR,YAAW,KAAK;EACd,KAAK;EACL,OAAO,EAAE,aAAa,MAAM,QAAQ;EACrC,CAAC;AAIJ,KAAI,MAAM,WACR,YAAW,KAAK;EACd,KAAK;EACL,OAAO,EAAE,aAAa,MAAM,YAAY;EACzC,CAAC;AAIJ,KAAI,OAAO,mBACT,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,mBAAmB,CAClE,YAAW,KAAK;EACd;EACA,OAAO,iBAAiB,MAAM;EAC/B,CAAC;AAIN,QAAO;;;;;;;;;;;;;;;;;;;;;;AAuBT,SAAgB,gBAAgB,WAAwF;AACtH,QAAO,OAAO,QAAuC;EACnD,MAAM,WAAW,MAAM,QAAQ,IAAI,GAAG,MAAM,CAAC,IAAI;AACjD,MAAI,SAAS,WAAW,EAAG;EAE3B,MAAM,gBAAgB,kBAAkB;EAExC,MAAM,YAAY,eAAe,OAAO;EACxC,MAAM,WAAW,eAAe;EAGhC,MAAM,0BAA8D;GAGlE,MAAM,aAAa,QAAQ,IAAI,8BAA8B,QAAQ,IAAI;AACzE,OAAI,YAAY;IACd,MAAM,UAAkC,EAAE;IAE1C,MAAM,UAAU,mBAAmB,WAAW;AAE9C,SAAK,MAAM,QAAQ,QAAQ,MAAM,IAAI,EAAE;KACrC,MAAM,UAAU,KAAK,QAAQ,IAAI;AACjC,SAAI,UAAU,GAAG;MACf,MAAM,MAAM,KAAK,MAAM,GAAG,QAAQ,CAAC,MAAM;MACzC,MAAM,QAAQ,KAAK,MAAM,UAAU,EAAE,CAAC,MAAM;AAC5C,UAAI,OAAO,MACT,SAAQ,OAAO;;;AAIrB,QAAI,OAAO,KAAK,QAAQ,CAAC,SAAS,EAAG,QAAO;;GAI9C,MAAM,OAAO,QAAQ,IAAI;AACzB,OAAI,KACF,QAAO,EAAE,eAAe,MAAM;;EAOlC,MAAM,SAA8B;GAClC,UAAU,WAAW,YAAY,WAAW,YAAY,UAAU,YAAY,QAAQ,IAAI,sBAAsB,QAAQ,IAAI;GAC5H,aAAa,WAAW,eAAe,WAAW,eAAe,UAAU,eAAe,QAAQ,IAAI,0BAA0B,QAAQ,IAAI;GAC5I,SAAS,WAAW,WAAW,WAAW,WAAW,UAAU,WAAW,mBAAmB;GAC7F,oBAAoB,WAAW,sBAAsB,WAAW,sBAAsB,UAAU;GAChG,SAAS,WAAW,WAAW,WAAW,WAAW,UAAU;GAChE;AAED,MAAI,CAAC,OAAO,UAAU;AACpB,WAAQ,MAAM,6HAA6H;AAC3I;;AAGF,MAAI;AACF,SAAM,gBAAgB,SAAS,KAAI,MAAK,EAAE,MAAM,EAAE,OAAqB;WAChE,OAAO;AACd,WAAQ,MAAM,+CAA+C,MAAM;;;;;;;;;;;;;;AAezE,eAAsB,WAAW,OAAkB,QAAmC;AACpF,OAAM,gBAAgB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;AAaxC,eAAsB,gBAAgB,QAAqB,QAAmC;AAC5F,KAAI,OAAO,WAAW,EAAG;CAEzB,MAAM,UAAU,OAAO,WAAW;CAClC,MAAM,MAAM,GAAG,OAAO,SAAS,QAAQ,OAAO,GAAG,CAAC;CAIlD,MAAM,CAAC,cAAc;CACrB,MAAM,qBAAqB,wBAAwB,YAAY,OAAO;CAEtE,MAAM,aAAa,OAAO,IAAI,gBAAgB;CAE9C,MAAM,UAAoC,EACxC,cAAc,CACZ;EACE,UAAU,EAAE,YAAY,oBAAoB;EAC5C,WAAW,CACT;GACE,OAAO;IAAE,MAAM;IAAS,SAAS;IAAS;GAC1C;GACD,CACF;EACF,CACF,EACF;CAED,MAAM,UAAkC;EACtC,gBAAgB;EAChB,GAAG,OAAO;EACX;CAED,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,QAAQ;AAE/D,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,QAAQ;GACR;GACA,MAAM,KAAK,UAAU,QAAQ;GAC7B,QAAQ,WAAW;GACpB,CAAC;AAEF,MAAI,CAAC,SAAS,IAAI;GAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,gBAAgB;GAC/D,MAAM,WAAW,KAAK,SAAS,MAAM,GAAG,KAAK,MAAM,GAAG,IAAI,CAAC,kBAAkB;AAC7E,SAAM,IAAI,MAAM,mBAAmB,SAAS,OAAO,GAAG,SAAS,WAAW,KAAK,WAAW;;WAEpF;AACR,eAAa,UAAU"}
@@ -1,23 +1,24 @@
1
- import { DrainContext, WideEvent } from '../types.mjs';
1
+ import { DrainContext, WideEvent } from "../types.mjs";
2
2
 
3
+ //#region src/adapters/posthog.d.ts
3
4
  interface PostHogConfig {
4
- /** PostHog project API key */
5
- apiKey: string;
6
- /** PostHog host URL. Default: https://us.i.posthog.com */
7
- host?: string;
8
- /** PostHog event name. Default: evlog_wide_event */
9
- eventName?: string;
10
- /** Override distinct_id (defaults to event.service) */
11
- distinctId?: string;
12
- /** Request timeout in milliseconds. Default: 5000 */
13
- timeout?: number;
5
+ /** PostHog project API key */
6
+ apiKey: string;
7
+ /** PostHog host URL. Default: https://us.i.posthog.com */
8
+ host?: string;
9
+ /** PostHog event name. Default: evlog_wide_event */
10
+ eventName?: string;
11
+ /** Override distinct_id (defaults to event.service) */
12
+ distinctId?: string;
13
+ /** Request timeout in milliseconds. Default: 5000 */
14
+ timeout?: number;
14
15
  }
15
16
  /** PostHog event structure for the batch API */
16
17
  interface PostHogEvent {
17
- event: string;
18
- distinct_id: string;
19
- timestamp: string;
20
- properties: Record<string, unknown>;
18
+ event: string;
19
+ distinct_id: string;
20
+ timestamp: string;
21
+ properties: Record<string, unknown>;
21
22
  }
22
23
  /**
23
24
  * Convert a WideEvent to a PostHog event format.
@@ -44,7 +45,7 @@ declare function toPostHogEvent(event: WideEvent, config: PostHogConfig): PostHo
44
45
  * }))
45
46
  * ```
46
47
  */
47
- declare function createPostHogDrain(overrides?: Partial<PostHogConfig>): (ctx: DrainContext) => Promise<void>;
48
+ declare function createPostHogDrain(overrides?: Partial<PostHogConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void>;
48
49
  /**
49
50
  * Send a single event to PostHog.
50
51
  *
@@ -67,6 +68,6 @@ declare function sendToPostHog(event: WideEvent, config: PostHogConfig): Promise
67
68
  * ```
68
69
  */
69
70
  declare function sendBatchToPostHog(events: WideEvent[], config: PostHogConfig): Promise<void>;
70
-
71
- export { createPostHogDrain, sendBatchToPostHog, sendToPostHog, toPostHogEvent };
72
- export type { PostHogConfig, PostHogEvent };
71
+ //#endregion
72
+ export { PostHogConfig, PostHogEvent, createPostHogDrain, sendBatchToPostHog, sendToPostHog, toPostHogEvent };
73
+ //# sourceMappingURL=posthog.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"posthog.d.mts","names":[],"sources":["../../src/adapters/posthog.ts"],"mappings":";;;UAGiB,aAAA;;EAEf,MAAA;EAF4B;EAI5B,IAAA;EAJ4B;EAM5B,SAAA;EAFA;EAIA,UAAA;EAAA;EAEA,OAAA;AAAA;;UAIe,YAAA;EACf,KAAA;EACA,WAAA;EACA,SAAA;EACA,UAAA,EAAY,MAAA;AAAA;;;;iBAME,cAAA,CAAe,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,aAAA,GAAgB,YAAA;;AAAzE;;;;;;;;;;;;;;;AAoCA;;;;;iBAAgB,kBAAA,CAAmB,SAAA,GAAY,OAAA,CAAQ,aAAA,KAAkB,GAAA,EAAK,YAAA,GAAe,YAAA,OAAmB,OAAA;;;;;;;;;;;iBA0C1F,aAAA,CAAc,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,aAAA,GAAgB,OAAA;;;AAA9E;;;;;;;;iBAcsB,kBAAA,CAAmB,MAAA,EAAQ,SAAA,IAAa,MAAA,EAAQ,aAAA,GAAgB,OAAA"}