@loglayer/transport-new-relic 2.1.6 → 2.1.8

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.cjs CHANGED
@@ -1,239 +1,261 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/NewRelicTransport.ts
2
- var _transport = require('@loglayer/transport');
3
- var MAX_PAYLOAD_SIZE = 1e6;
4
- var MAX_ATTRIBUTES = 255;
5
- var MAX_ATTRIBUTE_NAME_LENGTH = 255;
6
- var MAX_ATTRIBUTE_VALUE_LENGTH = 4094;
1
+ //#region rolldown:runtime
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 __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ let __loglayer_transport = require("@loglayer/transport");
25
+ __loglayer_transport = __toESM(__loglayer_transport);
26
+
27
+ //#region src/NewRelicTransport.ts
28
+ const MAX_PAYLOAD_SIZE = 1e6;
29
+ const MAX_ATTRIBUTES = 255;
30
+ const MAX_ATTRIBUTE_NAME_LENGTH = 255;
31
+ const MAX_ATTRIBUTE_VALUE_LENGTH = 4094;
32
+ /**
33
+ * Error thrown when log entry validation fails.
34
+ * This includes payload size, attribute count, and attribute name length validations.
35
+ */
7
36
  var ValidationError = class extends Error {
8
- constructor(message) {
9
- super(message);
10
- this.name = "ValidationError";
11
- }
37
+ constructor(message) {
38
+ super(message);
39
+ this.name = "ValidationError";
40
+ }
12
41
  };
42
+ /**
43
+ * Error thrown when New Relic's API rate limit is exceeded.
44
+ * Contains the retry-after duration specified by the API.
45
+ */
13
46
  var RateLimitError = class extends Error {
14
- constructor(message, retryAfter) {
15
- super(message);
16
- this.retryAfter = retryAfter;
17
- this.name = "RateLimitError";
18
- }
47
+ constructor(message, retryAfter) {
48
+ super(message);
49
+ this.retryAfter = retryAfter;
50
+ this.name = "RateLimitError";
51
+ }
19
52
  };
53
+ /**
54
+ * Validates a log entry against New Relic's constraints.
55
+ * - Checks number of attributes (max 255)
56
+ * - Validates attribute name length (max 255 characters)
57
+ * - Truncates attribute values longer than 4094 characters
58
+ *
59
+ * @param logEntry - The log entry to validate
60
+ * @returns The validated (and potentially modified) log entry
61
+ * @throws {ValidationError} If validation fails
62
+ */
20
63
  function validateLogEntry(logEntry) {
21
- if (logEntry.attributes) {
22
- const attributeCount = Object.keys(logEntry.attributes).length;
23
- if (attributeCount > MAX_ATTRIBUTES) {
24
- throw new ValidationError(
25
- `Log entry exceeds maximum number of attributes (${MAX_ATTRIBUTES}). Found: ${attributeCount}`
26
- );
27
- }
28
- for (const [key, value] of Object.entries(logEntry.attributes)) {
29
- if (key.length > MAX_ATTRIBUTE_NAME_LENGTH) {
30
- throw new ValidationError(
31
- `Attribute name '${key}' exceeds maximum length (${MAX_ATTRIBUTE_NAME_LENGTH}). Length: ${key.length}`
32
- );
33
- }
34
- if (typeof value === "string" && value.length > MAX_ATTRIBUTE_VALUE_LENGTH) {
35
- logEntry.attributes[key] = value.slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);
36
- }
37
- }
38
- }
39
- return logEntry;
64
+ if (logEntry.attributes) {
65
+ const attributeCount = Object.keys(logEntry.attributes).length;
66
+ if (attributeCount > MAX_ATTRIBUTES) throw new ValidationError(`Log entry exceeds maximum number of attributes (${MAX_ATTRIBUTES}). Found: ${attributeCount}`);
67
+ for (const [key, value] of Object.entries(logEntry.attributes)) {
68
+ if (key.length > MAX_ATTRIBUTE_NAME_LENGTH) throw new ValidationError(`Attribute name '${key}' exceeds maximum length (${MAX_ATTRIBUTE_NAME_LENGTH}). Length: ${key.length}`);
69
+ if (typeof value === "string" && value.length > MAX_ATTRIBUTE_VALUE_LENGTH) logEntry.attributes[key] = value.slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);
70
+ }
71
+ }
72
+ return logEntry;
40
73
  }
41
- var NewRelicTransport = class extends _transport.LoggerlessTransport {
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
- /**
51
- * Creates a new instance of NewRelicTransport.
52
- *
53
- * @param config - Configuration options for the transport
54
- * @param config.apiKey - New Relic API key for authentication
55
- * @param config.endpoint - Optional custom endpoint URL (defaults to New Relic's Log API endpoint)
56
- * @param config.onError - Optional error callback for handling errors
57
- * @param config.onDebug - Optional callback for debugging log entries before they are sent
58
- * @param config.useCompression - Whether to use gzip compression (defaults to true)
59
- * @param config.maxRetries - Maximum number of retry attempts (defaults to 3)
60
- * @param config.retryDelay - Base delay between retries in milliseconds (defaults to 1000)
61
- * @param config.respectRateLimit - Whether to honor rate limiting headers (defaults to true)
62
- */
63
- constructor(config) {
64
- super(config);
65
- this.apiKey = config.apiKey;
66
- this.endpoint = _nullishCoalesce(config.endpoint, () => ( "https://log-api.newrelic.com/log/v1"));
67
- this.onError = config.onError;
68
- this.onDebug = config.onDebug;
69
- this.useCompression = _nullishCoalesce(config.useCompression, () => ( true));
70
- this.maxRetries = _nullishCoalesce(config.maxRetries, () => ( 3));
71
- this.retryDelay = _nullishCoalesce(config.retryDelay, () => ( 1e3));
72
- this.respectRateLimit = _nullishCoalesce(config.respectRateLimit, () => ( true));
73
- }
74
- /**
75
- * Processes and ships log entries to New Relic.
76
- *
77
- * This method:
78
- * 1. Validates the message size
79
- * 2. Creates and validates the log entry
80
- * 3. Validates the final payload size
81
- * 4. Asynchronously sends the log entry to New Relic
82
- *
83
- * The actual sending is done asynchronously in a fire-and-forget manner to maintain
84
- * compatibility with the base transport class while still providing retry and error handling.
85
- *
86
- * @param params - Log parameters including level, messages, and metadata
87
- * @param params.logLevel - The severity level of the log
88
- * @param params.messages - Array of message strings to be joined
89
- * @param params.data - Optional metadata to include with the log
90
- * @param params.hasData - Whether metadata is present
91
- * @returns The original messages array
92
- * @throws {ValidationError} If the payload exceeds size limits or validation fails
93
- */
94
- shipToLogger({ logLevel, messages, data, hasData }) {
95
- try {
96
- const message = messages.join(" ");
97
- const messageBytes = new TextEncoder().encode(message).length;
98
- if (messageBytes > MAX_PAYLOAD_SIZE) {
99
- throw new ValidationError(
100
- `Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${messageBytes} bytes`
101
- );
102
- }
103
- const logEntry = {
104
- timestamp: Date.now(),
105
- level: logLevel,
106
- log: message
107
- };
108
- if (data && hasData) {
109
- Object.assign(logEntry, {
110
- attributes: data
111
- });
112
- }
113
- const validatedEntry = validateLogEntry(logEntry);
114
- if (this.onDebug) {
115
- this.onDebug(validatedEntry);
116
- }
117
- const payload = JSON.stringify([validatedEntry]);
118
- const payloadBytes = new TextEncoder().encode(payload).length;
119
- if (payloadBytes > MAX_PAYLOAD_SIZE) {
120
- throw new ValidationError(
121
- `Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`
122
- );
123
- }
124
- (async () => {
125
- try {
126
- await sendWithRetry(
127
- this.endpoint,
128
- this.apiKey,
129
- payload,
130
- this.useCompression,
131
- this.maxRetries,
132
- this.retryDelay,
133
- this.respectRateLimit
134
- );
135
- } catch (error) {
136
- if (this.onError) {
137
- this.onError(error instanceof Error ? error : new Error(String(error)));
138
- }
139
- if (error instanceof ValidationError) {
140
- throw error;
141
- }
142
- }
143
- })();
144
- } catch (error) {
145
- if (this.onError) {
146
- this.onError(error instanceof Error ? error : new Error(String(error)));
147
- }
148
- }
149
- return messages;
150
- }
74
+ /**
75
+ * NewRelicTransport is responsible for sending logs to New Relic's Log API.
76
+ * It handles validation, compression, retries, and rate limiting according to New Relic's specifications.
77
+ *
78
+ * Features:
79
+ * - Validates payload size (max 1MB)
80
+ * - Validates number of attributes (max 255)
81
+ * - Validates attribute name length (max 255 characters)
82
+ * - Truncates attribute values longer than 4094 characters
83
+ * - Supports gzip compression
84
+ * - Handles rate limiting with configurable behavior
85
+ * - Implements retry logic with exponential backoff
86
+ */
87
+ var NewRelicTransport = class extends __loglayer_transport.LoggerlessTransport {
88
+ apiKey;
89
+ endpoint;
90
+ onError;
91
+ onDebug;
92
+ useCompression;
93
+ maxRetries;
94
+ retryDelay;
95
+ respectRateLimit;
96
+ /**
97
+ * Creates a new instance of NewRelicTransport.
98
+ *
99
+ * @param config - Configuration options for the transport
100
+ * @param config.apiKey - New Relic API key for authentication
101
+ * @param config.endpoint - Optional custom endpoint URL (defaults to New Relic's Log API endpoint)
102
+ * @param config.onError - Optional error callback for handling errors
103
+ * @param config.onDebug - Optional callback for debugging log entries before they are sent
104
+ * @param config.useCompression - Whether to use gzip compression (defaults to true)
105
+ * @param config.maxRetries - Maximum number of retry attempts (defaults to 3)
106
+ * @param config.retryDelay - Base delay between retries in milliseconds (defaults to 1000)
107
+ * @param config.respectRateLimit - Whether to honor rate limiting headers (defaults to true)
108
+ */
109
+ constructor(config) {
110
+ super(config);
111
+ this.apiKey = config.apiKey;
112
+ this.endpoint = config.endpoint ?? "https://log-api.newrelic.com/log/v1";
113
+ this.onError = config.onError;
114
+ this.onDebug = config.onDebug;
115
+ this.useCompression = config.useCompression ?? true;
116
+ this.maxRetries = config.maxRetries ?? 3;
117
+ this.retryDelay = config.retryDelay ?? 1e3;
118
+ this.respectRateLimit = config.respectRateLimit ?? true;
119
+ }
120
+ /**
121
+ * Processes and ships log entries to New Relic.
122
+ *
123
+ * This method:
124
+ * 1. Validates the message size
125
+ * 2. Creates and validates the log entry
126
+ * 3. Validates the final payload size
127
+ * 4. Asynchronously sends the log entry to New Relic
128
+ *
129
+ * The actual sending is done asynchronously in a fire-and-forget manner to maintain
130
+ * compatibility with the base transport class while still providing retry and error handling.
131
+ *
132
+ * @param params - Log parameters including level, messages, and metadata
133
+ * @param params.logLevel - The severity level of the log
134
+ * @param params.messages - Array of message strings to be joined
135
+ * @param params.data - Optional metadata to include with the log
136
+ * @param params.hasData - Whether metadata is present
137
+ * @returns The original messages array
138
+ * @throws {ValidationError} If the payload exceeds size limits or validation fails
139
+ */
140
+ shipToLogger({ logLevel, messages, data, hasData }) {
141
+ try {
142
+ const message = messages.join(" ");
143
+ const messageBytes = new TextEncoder().encode(message).length;
144
+ if (messageBytes > MAX_PAYLOAD_SIZE) throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${messageBytes} bytes`);
145
+ const logEntry = {
146
+ timestamp: Date.now(),
147
+ level: logLevel,
148
+ log: message
149
+ };
150
+ if (data && hasData) Object.assign(logEntry, { attributes: data });
151
+ const validatedEntry = validateLogEntry(logEntry);
152
+ if (this.onDebug) this.onDebug(validatedEntry);
153
+ const payload = JSON.stringify([validatedEntry]);
154
+ const payloadBytes = new TextEncoder().encode(payload).length;
155
+ if (payloadBytes > MAX_PAYLOAD_SIZE) throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`);
156
+ (async () => {
157
+ try {
158
+ await sendWithRetry(this.endpoint, this.apiKey, payload, this.useCompression, this.maxRetries, this.retryDelay, this.respectRateLimit);
159
+ } catch (error) {
160
+ if (this.onError) this.onError(error instanceof Error ? error : new Error(String(error)));
161
+ if (error instanceof ValidationError) throw error;
162
+ }
163
+ })();
164
+ } catch (error) {
165
+ if (this.onError) this.onError(error instanceof Error ? error : new Error(String(error)));
166
+ }
167
+ return messages;
168
+ }
151
169
  };
170
+ /**
171
+ * Compresses data using gzip compression.
172
+ *
173
+ * @param data - The string data to compress
174
+ * @returns A promise that resolves to the compressed data as a Uint8Array
175
+ */
152
176
  async function compressData(data) {
153
- const stream = new CompressionStream("gzip");
154
- const writer = stream.writable.getWriter();
155
- const encoder = new TextEncoder();
156
- const chunks = [];
157
- await writer.write(encoder.encode(data));
158
- await writer.close();
159
- const reader = stream.readable.getReader();
160
- while (true) {
161
- const { value, done } = await reader.read();
162
- if (done) break;
163
- chunks.push(value);
164
- }
165
- const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
166
- const result = new Uint8Array(totalLength);
167
- let offset = 0;
168
- for (const chunk of chunks) {
169
- result.set(chunk, offset);
170
- offset += chunk.length;
171
- }
172
- return result;
177
+ const stream = new CompressionStream("gzip");
178
+ const writer = stream.writable.getWriter();
179
+ const encoder = new TextEncoder();
180
+ const chunks = [];
181
+ await writer.write(encoder.encode(data));
182
+ await writer.close();
183
+ const reader = stream.readable.getReader();
184
+ while (true) {
185
+ const { value, done } = await reader.read();
186
+ if (done) break;
187
+ chunks.push(value);
188
+ }
189
+ const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
190
+ const result = new Uint8Array(totalLength);
191
+ let offset = 0;
192
+ for (const chunk of chunks) {
193
+ result.set(chunk, offset);
194
+ offset += chunk.length;
195
+ }
196
+ return result;
173
197
  }
198
+ /**
199
+ * Sends a log entry to New Relic with retry logic.
200
+ * Handles rate limiting, compression, and error cases.
201
+ *
202
+ * @param endpoint - The New Relic API endpoint
203
+ * @param apiKey - The New Relic API key
204
+ * @param payload - The JSON payload to send
205
+ * @param useCompression - Whether to use gzip compression
206
+ * @param maxRetries - Maximum number of retry attempts
207
+ * @param retryDelay - Base delay between retries in milliseconds
208
+ * @param respectRateLimit - Whether to honor rate limiting headers
209
+ * @returns A promise that resolves to the API response
210
+ * @throws {ValidationError} If payload validation fails
211
+ * @throws {RateLimitError} If rate limited and not respecting rate limits
212
+ * @throws {Error} If the request fails after all retries
213
+ */
174
214
  async function sendWithRetry(endpoint, apiKey, payload, useCompression, maxRetries, retryDelay, respectRateLimit = true) {
175
- const payloadBytes = new TextEncoder().encode(payload).length;
176
- if (payloadBytes > MAX_PAYLOAD_SIZE) {
177
- throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`);
178
- }
179
- let lastError;
180
- let compressedPayload;
181
- if (useCompression) {
182
- compressedPayload = await compressData(payload);
183
- if (compressedPayload.length > MAX_PAYLOAD_SIZE) {
184
- throw new ValidationError(
185
- `Compressed payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${compressedPayload.length} bytes`
186
- );
187
- }
188
- }
189
- const headers = {
190
- "Content-Type": "application/json",
191
- "Api-Key": apiKey
192
- };
193
- if (useCompression) {
194
- headers["Content-Encoding"] = "gzip";
195
- }
196
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
197
- try {
198
- const response = await fetch(endpoint, {
199
- method: "POST",
200
- headers,
201
- body: useCompression ? compressedPayload : payload
202
- });
203
- if (response.status === 429) {
204
- const retryAfter = Number.parseInt(response.headers.get("Retry-After") || "60", 10);
205
- if (respectRateLimit) {
206
- await new Promise((resolve) => setTimeout(resolve, retryAfter * 1e3));
207
- attempt--;
208
- continue;
209
- }
210
- throw new RateLimitError(`Rate limit exceeded. Retry after ${retryAfter} seconds`, retryAfter);
211
- }
212
- if (!response.ok) {
213
- throw new Error(`Failed to send logs to New Relic: ${response.statusText}`);
214
- }
215
- return response;
216
- } catch (error) {
217
- lastError = error instanceof Error ? error : new Error(String(error));
218
- if (error instanceof ValidationError) {
219
- throw error;
220
- }
221
- if (!respectRateLimit && error instanceof RateLimitError) {
222
- throw error;
223
- }
224
- if (attempt === maxRetries) {
225
- throw new Error(`Failed to send logs after ${maxRetries} retries: ${lastError.message}`);
226
- }
227
- if (!(error instanceof RateLimitError)) {
228
- const jitter = Math.random() * 200;
229
- const delay = retryDelay * 2 ** attempt + jitter;
230
- await new Promise((resolve) => setTimeout(resolve, delay));
231
- }
232
- }
233
- }
234
- throw lastError;
215
+ const payloadBytes = new TextEncoder().encode(payload).length;
216
+ if (payloadBytes > MAX_PAYLOAD_SIZE) throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`);
217
+ let lastError;
218
+ let compressedPayload;
219
+ if (useCompression) {
220
+ compressedPayload = await compressData(payload);
221
+ if (compressedPayload.length > MAX_PAYLOAD_SIZE) throw new ValidationError(`Compressed payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${compressedPayload.length} bytes`);
222
+ }
223
+ const headers = {
224
+ "Content-Type": "application/json",
225
+ "Api-Key": apiKey
226
+ };
227
+ if (useCompression) headers["Content-Encoding"] = "gzip";
228
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
229
+ const response = await fetch(endpoint, {
230
+ method: "POST",
231
+ headers,
232
+ body: useCompression ? compressedPayload : payload
233
+ });
234
+ if (response.status === 429) {
235
+ const retryAfter = Number.parseInt(response.headers.get("Retry-After") || "60", 10);
236
+ if (respectRateLimit) {
237
+ await new Promise((resolve) => setTimeout(resolve, retryAfter * 1e3));
238
+ attempt--;
239
+ continue;
240
+ }
241
+ throw new RateLimitError(`Rate limit exceeded. Retry after ${retryAfter} seconds`, retryAfter);
242
+ }
243
+ if (!response.ok) throw new Error(`Failed to send logs to New Relic: ${response.statusText}`);
244
+ return response;
245
+ } catch (error) {
246
+ lastError = error instanceof Error ? error : new Error(String(error));
247
+ if (error instanceof ValidationError) throw error;
248
+ if (!respectRateLimit && error instanceof RateLimitError) throw error;
249
+ if (attempt === maxRetries) throw new Error(`Failed to send logs after ${maxRetries} retries: ${lastError.message}`);
250
+ if (!(error instanceof RateLimitError)) {
251
+ const jitter = Math.random() * 200;
252
+ const delay = retryDelay * 2 ** attempt + jitter;
253
+ await new Promise((resolve) => setTimeout(resolve, delay));
254
+ }
255
+ }
256
+ throw lastError;
235
257
  }
236
258
 
237
-
259
+ //#endregion
238
260
  exports.NewRelicTransport = NewRelicTransport;
239
261
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/loglayer/loglayer/packages/transports/new-relic/dist/index.cjs","../src/NewRelicTransport.ts"],"names":[],"mappings":"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","file":"/home/runner/work/loglayer/loglayer/packages/transports/new-relic/dist/index.cjs","sourcesContent":[null,"import type { LoggerlessTransportConfig, LogLayerTransportParams } from \"@loglayer/transport\";\nimport { LoggerlessTransport } from \"@loglayer/transport\";\n\n// Constants defining New Relic's API limits\nconst MAX_PAYLOAD_SIZE = 1_000_000; // 1MB in bytes\nconst MAX_ATTRIBUTES = 255;\nconst MAX_ATTRIBUTE_NAME_LENGTH = 255;\nconst MAX_ATTRIBUTE_VALUE_LENGTH = 4094;\n\n/**\n * Error thrown when log entry validation fails.\n * This includes payload size, attribute count, and attribute name length validations.\n */\nclass ValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ValidationError\";\n }\n}\n\n/**\n * Error thrown when New Relic's API rate limit is exceeded.\n * Contains the retry-after duration specified by the API.\n */\nclass RateLimitError extends Error {\n constructor(\n message: string,\n public retryAfter: number,\n ) {\n super(message);\n this.name = \"RateLimitError\";\n }\n}\n\n/**\n * Configuration options for the New Relic transport.\n */\nexport interface NewRelicTransportConfig extends LoggerlessTransportConfig {\n /**\n * The New Relic API key\n */\n apiKey: string;\n /**\n * The New Relic Log API endpoint\n * @default https://log-api.newrelic.com/log/v1\n */\n endpoint?: string;\n /**\n * Optional callback for error handling\n */\n onError?: (err: Error) => void;\n /**\n * Optional callback for debugging log entries before they are sent\n */\n onDebug?: (entry: Record<string, any>) => void;\n /**\n * Whether to use gzip compression\n * @default true\n */\n useCompression?: boolean;\n /**\n * Number of retry attempts before giving up\n * @default 3\n */\n maxRetries?: number;\n /**\n * Base delay between retries in milliseconds\n * @default 1000\n */\n retryDelay?: number;\n /**\n * Whether to respect rate limiting by waiting when a 429 response is received\n * @default true\n */\n respectRateLimit?: boolean;\n}\n\n/**\n * Validates a log entry against New Relic's constraints.\n * - Checks number of attributes (max 255)\n * - Validates attribute name length (max 255 characters)\n * - Truncates attribute values longer than 4094 characters\n *\n * @param logEntry - The log entry to validate\n * @returns The validated (and potentially modified) log entry\n * @throws {ValidationError} If validation fails\n */\nfunction validateLogEntry(logEntry: Record<string, any>) {\n if (logEntry.attributes) {\n // Check number of attributes\n const attributeCount = Object.keys(logEntry.attributes).length;\n if (attributeCount > MAX_ATTRIBUTES) {\n throw new ValidationError(\n `Log entry exceeds maximum number of attributes (${MAX_ATTRIBUTES}). Found: ${attributeCount}`,\n );\n }\n\n // Check attribute names and values\n for (const [key, value] of Object.entries(logEntry.attributes)) {\n // Check attribute name length\n if (key.length > MAX_ATTRIBUTE_NAME_LENGTH) {\n throw new ValidationError(\n `Attribute name '${key}' exceeds maximum length (${MAX_ATTRIBUTE_NAME_LENGTH}). Length: ${key.length}`,\n );\n }\n\n // Check string value length\n if (typeof value === \"string\" && value.length > MAX_ATTRIBUTE_VALUE_LENGTH) {\n // Truncate the string value to the maximum length\n logEntry.attributes[key] = value.slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);\n }\n }\n }\n\n return logEntry;\n}\n\n/**\n * NewRelicTransport is responsible for sending logs to New Relic's Log API.\n * It handles validation, compression, retries, and rate limiting according to New Relic's specifications.\n *\n * Features:\n * - Validates payload size (max 1MB)\n * - Validates number of attributes (max 255)\n * - Validates attribute name length (max 255 characters)\n * - Truncates attribute values longer than 4094 characters\n * - Supports gzip compression\n * - Handles rate limiting with configurable behavior\n * - Implements retry logic with exponential backoff\n */\nexport class NewRelicTransport extends LoggerlessTransport {\n private apiKey: string;\n private endpoint: string;\n private onError?: (err: Error) => void;\n private onDebug?: (entry: Record<string, any>) => void;\n private useCompression: boolean;\n private maxRetries: number;\n private retryDelay: number;\n private respectRateLimit: boolean;\n\n /**\n * Creates a new instance of NewRelicTransport.\n *\n * @param config - Configuration options for the transport\n * @param config.apiKey - New Relic API key for authentication\n * @param config.endpoint - Optional custom endpoint URL (defaults to New Relic's Log API endpoint)\n * @param config.onError - Optional error callback for handling errors\n * @param config.onDebug - Optional callback for debugging log entries before they are sent\n * @param config.useCompression - Whether to use gzip compression (defaults to true)\n * @param config.maxRetries - Maximum number of retry attempts (defaults to 3)\n * @param config.retryDelay - Base delay between retries in milliseconds (defaults to 1000)\n * @param config.respectRateLimit - Whether to honor rate limiting headers (defaults to true)\n */\n constructor(config: NewRelicTransportConfig) {\n super(config);\n\n this.apiKey = config.apiKey;\n this.endpoint = config.endpoint ?? \"https://log-api.newrelic.com/log/v1\";\n this.onError = config.onError;\n this.onDebug = config.onDebug;\n this.useCompression = config.useCompression ?? true;\n this.maxRetries = config.maxRetries ?? 3;\n this.retryDelay = config.retryDelay ?? 1000;\n this.respectRateLimit = config.respectRateLimit ?? true;\n }\n\n /**\n * Processes and ships log entries to New Relic.\n *\n * This method:\n * 1. Validates the message size\n * 2. Creates and validates the log entry\n * 3. Validates the final payload size\n * 4. Asynchronously sends the log entry to New Relic\n *\n * The actual sending is done asynchronously in a fire-and-forget manner to maintain\n * compatibility with the base transport class while still providing retry and error handling.\n *\n * @param params - Log parameters including level, messages, and metadata\n * @param params.logLevel - The severity level of the log\n * @param params.messages - Array of message strings to be joined\n * @param params.data - Optional metadata to include with the log\n * @param params.hasData - Whether metadata is present\n * @returns The original messages array\n * @throws {ValidationError} If the payload exceeds size limits or validation fails\n */\n shipToLogger({ logLevel, messages, data, hasData }: LogLayerTransportParams): any[] {\n try {\n // Check message size first\n const message = messages.join(\" \");\n const messageBytes = new TextEncoder().encode(message).length;\n if (messageBytes > MAX_PAYLOAD_SIZE) {\n throw new ValidationError(\n `Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${messageBytes} bytes`,\n );\n }\n\n const logEntry: Record<string, any> = {\n timestamp: Date.now(),\n level: logLevel,\n log: message,\n };\n\n if (data && hasData) {\n Object.assign(logEntry, {\n attributes: data,\n });\n }\n\n const validatedEntry = validateLogEntry(logEntry);\n\n // Call onDebug callback if defined\n if (this.onDebug) {\n this.onDebug(validatedEntry);\n }\n\n // Check final payload size\n const payload = JSON.stringify([validatedEntry]);\n const payloadBytes = new TextEncoder().encode(payload).length;\n if (payloadBytes > MAX_PAYLOAD_SIZE) {\n throw new ValidationError(\n `Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`,\n );\n }\n\n // Fire and forget the async processing\n (async () => {\n try {\n await sendWithRetry(\n this.endpoint,\n this.apiKey,\n payload,\n this.useCompression,\n this.maxRetries,\n this.retryDelay,\n this.respectRateLimit,\n );\n } catch (error) {\n if (this.onError) {\n this.onError(error instanceof Error ? error : new Error(String(error)));\n }\n // Re-throw validation errors to prevent further processing\n if (error instanceof ValidationError) {\n throw error;\n }\n }\n })();\n } catch (error) {\n if (this.onError) {\n this.onError(error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n return messages;\n }\n}\n\n/**\n * Compresses data using gzip compression.\n *\n * @param data - The string data to compress\n * @returns A promise that resolves to the compressed data as a Uint8Array\n */\nasync function compressData(data: string): Promise<Uint8Array> {\n const stream = new CompressionStream(\"gzip\");\n const writer = stream.writable.getWriter();\n const encoder = new TextEncoder();\n const chunks: Uint8Array[] = [];\n\n await writer.write(encoder.encode(data));\n await writer.close();\n\n const reader = stream.readable.getReader();\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n\n // Combine all chunks into a single Uint8Array\n const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n}\n\n/**\n * Sends a log entry to New Relic with retry logic.\n * Handles rate limiting, compression, and error cases.\n *\n * @param endpoint - The New Relic API endpoint\n * @param apiKey - The New Relic API key\n * @param payload - The JSON payload to send\n * @param useCompression - Whether to use gzip compression\n * @param maxRetries - Maximum number of retry attempts\n * @param retryDelay - Base delay between retries in milliseconds\n * @param respectRateLimit - Whether to honor rate limiting headers\n * @returns A promise that resolves to the API response\n * @throws {ValidationError} If payload validation fails\n * @throws {RateLimitError} If rate limited and not respecting rate limits\n * @throws {Error} If the request fails after all retries\n */\nasync function sendWithRetry(\n endpoint: string,\n apiKey: string,\n payload: string,\n useCompression: boolean,\n maxRetries: number,\n retryDelay: number,\n respectRateLimit = true,\n): Promise<Response> {\n // Check payload size before compression\n const payloadBytes = new TextEncoder().encode(payload).length;\n if (payloadBytes > MAX_PAYLOAD_SIZE) {\n throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`);\n }\n\n let lastError: Error;\n let compressedPayload: Uint8Array | undefined;\n\n if (useCompression) {\n compressedPayload = await compressData(payload);\n // Check compressed payload size\n if (compressedPayload.length > MAX_PAYLOAD_SIZE) {\n throw new ValidationError(\n `Compressed payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${compressedPayload.length} bytes`,\n );\n }\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"Api-Key\": apiKey,\n };\n\n if (useCompression) {\n headers[\"Content-Encoding\"] = \"gzip\";\n }\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers,\n body: useCompression ? compressedPayload : payload,\n });\n\n if (response.status === 429) {\n const retryAfter = Number.parseInt(response.headers.get(\"Retry-After\") || \"60\", 10);\n if (respectRateLimit) {\n // Wait for the specified time before retrying\n await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));\n // Don't count rate limit retries against maxRetries\n attempt--;\n continue;\n }\n\n throw new RateLimitError(`Rate limit exceeded. Retry after ${retryAfter} seconds`, retryAfter);\n }\n\n if (!response.ok) {\n throw new Error(`Failed to send logs to New Relic: ${response.statusText}`);\n }\n\n return response;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Don't retry validation errors\n if (error instanceof ValidationError) {\n throw error;\n }\n\n // If we're not respecting rate limits, don't retry rate limit errors\n if (!respectRateLimit && error instanceof RateLimitError) {\n throw error;\n }\n\n if (attempt === maxRetries) {\n throw new Error(`Failed to send logs after ${maxRetries} retries: ${lastError.message}`);\n }\n\n // For non-rate-limit errors, use exponential backoff with jitter\n if (!(error instanceof RateLimitError)) {\n const jitter = Math.random() * 200;\n const delay = retryDelay * 2 ** attempt + jitter;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n throw lastError!;\n}\n"]}
1
+ {"version":3,"file":"index.cjs","names":["retryAfter: number","LoggerlessTransport","logEntry: Record<string, any>","chunks: Uint8Array[]","lastError: Error","compressedPayload: Uint8Array | undefined","headers: Record<string, string>"],"sources":["../src/NewRelicTransport.ts"],"sourcesContent":["import type { LoggerlessTransportConfig, LogLayerTransportParams } from \"@loglayer/transport\";\nimport { LoggerlessTransport } from \"@loglayer/transport\";\n\n// Constants defining New Relic's API limits\nconst MAX_PAYLOAD_SIZE = 1_000_000; // 1MB in bytes\nconst MAX_ATTRIBUTES = 255;\nconst MAX_ATTRIBUTE_NAME_LENGTH = 255;\nconst MAX_ATTRIBUTE_VALUE_LENGTH = 4094;\n\n/**\n * Error thrown when log entry validation fails.\n * This includes payload size, attribute count, and attribute name length validations.\n */\nclass ValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ValidationError\";\n }\n}\n\n/**\n * Error thrown when New Relic's API rate limit is exceeded.\n * Contains the retry-after duration specified by the API.\n */\nclass RateLimitError extends Error {\n constructor(\n message: string,\n public retryAfter: number,\n ) {\n super(message);\n this.name = \"RateLimitError\";\n }\n}\n\n/**\n * Configuration options for the New Relic transport.\n */\nexport interface NewRelicTransportConfig extends LoggerlessTransportConfig {\n /**\n * The New Relic API key\n */\n apiKey: string;\n /**\n * The New Relic Log API endpoint\n * @default https://log-api.newrelic.com/log/v1\n */\n endpoint?: string;\n /**\n * Optional callback for error handling\n */\n onError?: (err: Error) => void;\n /**\n * Optional callback for debugging log entries before they are sent\n */\n onDebug?: (entry: Record<string, any>) => void;\n /**\n * Whether to use gzip compression\n * @default true\n */\n useCompression?: boolean;\n /**\n * Number of retry attempts before giving up\n * @default 3\n */\n maxRetries?: number;\n /**\n * Base delay between retries in milliseconds\n * @default 1000\n */\n retryDelay?: number;\n /**\n * Whether to respect rate limiting by waiting when a 429 response is received\n * @default true\n */\n respectRateLimit?: boolean;\n}\n\n/**\n * Validates a log entry against New Relic's constraints.\n * - Checks number of attributes (max 255)\n * - Validates attribute name length (max 255 characters)\n * - Truncates attribute values longer than 4094 characters\n *\n * @param logEntry - The log entry to validate\n * @returns The validated (and potentially modified) log entry\n * @throws {ValidationError} If validation fails\n */\nfunction validateLogEntry(logEntry: Record<string, any>) {\n if (logEntry.attributes) {\n // Check number of attributes\n const attributeCount = Object.keys(logEntry.attributes).length;\n if (attributeCount > MAX_ATTRIBUTES) {\n throw new ValidationError(\n `Log entry exceeds maximum number of attributes (${MAX_ATTRIBUTES}). Found: ${attributeCount}`,\n );\n }\n\n // Check attribute names and values\n for (const [key, value] of Object.entries(logEntry.attributes)) {\n // Check attribute name length\n if (key.length > MAX_ATTRIBUTE_NAME_LENGTH) {\n throw new ValidationError(\n `Attribute name '${key}' exceeds maximum length (${MAX_ATTRIBUTE_NAME_LENGTH}). Length: ${key.length}`,\n );\n }\n\n // Check string value length\n if (typeof value === \"string\" && value.length > MAX_ATTRIBUTE_VALUE_LENGTH) {\n // Truncate the string value to the maximum length\n logEntry.attributes[key] = value.slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);\n }\n }\n }\n\n return logEntry;\n}\n\n/**\n * NewRelicTransport is responsible for sending logs to New Relic's Log API.\n * It handles validation, compression, retries, and rate limiting according to New Relic's specifications.\n *\n * Features:\n * - Validates payload size (max 1MB)\n * - Validates number of attributes (max 255)\n * - Validates attribute name length (max 255 characters)\n * - Truncates attribute values longer than 4094 characters\n * - Supports gzip compression\n * - Handles rate limiting with configurable behavior\n * - Implements retry logic with exponential backoff\n */\nexport class NewRelicTransport extends LoggerlessTransport {\n private apiKey: string;\n private endpoint: string;\n private onError?: (err: Error) => void;\n private onDebug?: (entry: Record<string, any>) => void;\n private useCompression: boolean;\n private maxRetries: number;\n private retryDelay: number;\n private respectRateLimit: boolean;\n\n /**\n * Creates a new instance of NewRelicTransport.\n *\n * @param config - Configuration options for the transport\n * @param config.apiKey - New Relic API key for authentication\n * @param config.endpoint - Optional custom endpoint URL (defaults to New Relic's Log API endpoint)\n * @param config.onError - Optional error callback for handling errors\n * @param config.onDebug - Optional callback for debugging log entries before they are sent\n * @param config.useCompression - Whether to use gzip compression (defaults to true)\n * @param config.maxRetries - Maximum number of retry attempts (defaults to 3)\n * @param config.retryDelay - Base delay between retries in milliseconds (defaults to 1000)\n * @param config.respectRateLimit - Whether to honor rate limiting headers (defaults to true)\n */\n constructor(config: NewRelicTransportConfig) {\n super(config);\n\n this.apiKey = config.apiKey;\n this.endpoint = config.endpoint ?? \"https://log-api.newrelic.com/log/v1\";\n this.onError = config.onError;\n this.onDebug = config.onDebug;\n this.useCompression = config.useCompression ?? true;\n this.maxRetries = config.maxRetries ?? 3;\n this.retryDelay = config.retryDelay ?? 1000;\n this.respectRateLimit = config.respectRateLimit ?? true;\n }\n\n /**\n * Processes and ships log entries to New Relic.\n *\n * This method:\n * 1. Validates the message size\n * 2. Creates and validates the log entry\n * 3. Validates the final payload size\n * 4. Asynchronously sends the log entry to New Relic\n *\n * The actual sending is done asynchronously in a fire-and-forget manner to maintain\n * compatibility with the base transport class while still providing retry and error handling.\n *\n * @param params - Log parameters including level, messages, and metadata\n * @param params.logLevel - The severity level of the log\n * @param params.messages - Array of message strings to be joined\n * @param params.data - Optional metadata to include with the log\n * @param params.hasData - Whether metadata is present\n * @returns The original messages array\n * @throws {ValidationError} If the payload exceeds size limits or validation fails\n */\n shipToLogger({ logLevel, messages, data, hasData }: LogLayerTransportParams): any[] {\n try {\n // Check message size first\n const message = messages.join(\" \");\n const messageBytes = new TextEncoder().encode(message).length;\n if (messageBytes > MAX_PAYLOAD_SIZE) {\n throw new ValidationError(\n `Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${messageBytes} bytes`,\n );\n }\n\n const logEntry: Record<string, any> = {\n timestamp: Date.now(),\n level: logLevel,\n log: message,\n };\n\n if (data && hasData) {\n Object.assign(logEntry, {\n attributes: data,\n });\n }\n\n const validatedEntry = validateLogEntry(logEntry);\n\n // Call onDebug callback if defined\n if (this.onDebug) {\n this.onDebug(validatedEntry);\n }\n\n // Check final payload size\n const payload = JSON.stringify([validatedEntry]);\n const payloadBytes = new TextEncoder().encode(payload).length;\n if (payloadBytes > MAX_PAYLOAD_SIZE) {\n throw new ValidationError(\n `Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`,\n );\n }\n\n // Fire and forget the async processing\n (async () => {\n try {\n await sendWithRetry(\n this.endpoint,\n this.apiKey,\n payload,\n this.useCompression,\n this.maxRetries,\n this.retryDelay,\n this.respectRateLimit,\n );\n } catch (error) {\n if (this.onError) {\n this.onError(error instanceof Error ? error : new Error(String(error)));\n }\n // Re-throw validation errors to prevent further processing\n if (error instanceof ValidationError) {\n throw error;\n }\n }\n })();\n } catch (error) {\n if (this.onError) {\n this.onError(error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n return messages;\n }\n}\n\n/**\n * Compresses data using gzip compression.\n *\n * @param data - The string data to compress\n * @returns A promise that resolves to the compressed data as a Uint8Array\n */\nasync function compressData(data: string): Promise<Uint8Array> {\n const stream = new CompressionStream(\"gzip\");\n const writer = stream.writable.getWriter();\n const encoder = new TextEncoder();\n const chunks: Uint8Array[] = [];\n\n await writer.write(encoder.encode(data));\n await writer.close();\n\n const reader = stream.readable.getReader();\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n\n // Combine all chunks into a single Uint8Array\n const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n}\n\n/**\n * Sends a log entry to New Relic with retry logic.\n * Handles rate limiting, compression, and error cases.\n *\n * @param endpoint - The New Relic API endpoint\n * @param apiKey - The New Relic API key\n * @param payload - The JSON payload to send\n * @param useCompression - Whether to use gzip compression\n * @param maxRetries - Maximum number of retry attempts\n * @param retryDelay - Base delay between retries in milliseconds\n * @param respectRateLimit - Whether to honor rate limiting headers\n * @returns A promise that resolves to the API response\n * @throws {ValidationError} If payload validation fails\n * @throws {RateLimitError} If rate limited and not respecting rate limits\n * @throws {Error} If the request fails after all retries\n */\nasync function sendWithRetry(\n endpoint: string,\n apiKey: string,\n payload: string,\n useCompression: boolean,\n maxRetries: number,\n retryDelay: number,\n respectRateLimit = true,\n): Promise<Response> {\n // Check payload size before compression\n const payloadBytes = new TextEncoder().encode(payload).length;\n if (payloadBytes > MAX_PAYLOAD_SIZE) {\n throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`);\n }\n\n let lastError: Error;\n let compressedPayload: Uint8Array | undefined;\n\n if (useCompression) {\n compressedPayload = await compressData(payload);\n // Check compressed payload size\n if (compressedPayload.length > MAX_PAYLOAD_SIZE) {\n throw new ValidationError(\n `Compressed payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${compressedPayload.length} bytes`,\n );\n }\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"Api-Key\": apiKey,\n };\n\n if (useCompression) {\n headers[\"Content-Encoding\"] = \"gzip\";\n }\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers,\n body: useCompression ? compressedPayload : payload,\n });\n\n if (response.status === 429) {\n const retryAfter = Number.parseInt(response.headers.get(\"Retry-After\") || \"60\", 10);\n if (respectRateLimit) {\n // Wait for the specified time before retrying\n await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));\n // Don't count rate limit retries against maxRetries\n attempt--;\n continue;\n }\n\n throw new RateLimitError(`Rate limit exceeded. Retry after ${retryAfter} seconds`, retryAfter);\n }\n\n if (!response.ok) {\n throw new Error(`Failed to send logs to New Relic: ${response.statusText}`);\n }\n\n return response;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Don't retry validation errors\n if (error instanceof ValidationError) {\n throw error;\n }\n\n // If we're not respecting rate limits, don't retry rate limit errors\n if (!respectRateLimit && error instanceof RateLimitError) {\n throw error;\n }\n\n if (attempt === maxRetries) {\n throw new Error(`Failed to send logs after ${maxRetries} retries: ${lastError.message}`);\n }\n\n // For non-rate-limit errors, use exponential backoff with jitter\n if (!(error instanceof RateLimitError)) {\n const jitter = Math.random() * 200;\n const delay = retryDelay * 2 ** attempt + jitter;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n throw lastError!;\n}\n"],"mappings":"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"}