@loglayer/transport-new-relic 1.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Theo Gravity
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # New Relic Transport for LogLayer
2
+
3
+ [![NPM Version](https://img.shields.io/npm/v/%40loglayer%2Ftransport-new-relic)](https://www.npmjs.com/package/@loglayer/transport-new-relic)
4
+ [![NPM Downloads](https://img.shields.io/npm/dm/%40loglayer%2Ftransport-new-relic)](https://www.npmjs.com/package/@loglayer/transport-new-relic)
5
+ [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
6
+
7
+ Ships logs to New Relic using their [Log API](https://docs.newrelic.com/docs/logs/log-api/introduction-log-api/). Features include:
8
+ - Automatic gzip compression (configurable)
9
+ - Retry mechanism with exponential backoff
10
+ - Rate limiting support with configurable behavior
11
+ - Validation of New Relic's API constraints
12
+ - Error handling callback
13
+ - Configurable endpoints for different regions
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install loglayer @loglayer/transport-new-relic serialize-error
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```typescript
24
+ import { LogLayer } from 'loglayer'
25
+ import { NewRelicTransport } from "@loglayer/transport-new-relic"
26
+ import { serializeError } from "serialize-error";
27
+
28
+ const log = new LogLayer({
29
+ errorSerializer: serializeError,
30
+ transport: new NewRelicTransport({
31
+ apiKey: "YOUR_NEW_RELIC_API_KEY",
32
+ endpoint: "https://log-api.newrelic.com/log/v1", // optional, this is the default
33
+ useCompression: true, // optional, defaults to true
34
+ maxRetries: 3, // optional, defaults to 3
35
+ retryDelay: 1000, // optional, base delay in ms, defaults to 1000
36
+ respectRateLimit: true, // optional, defaults to true
37
+ onError: (err) => {
38
+ console.error('Failed to send logs to New Relic:', err);
39
+ },
40
+ onDebug: (entry) => {
41
+ console.log('Log entry being sent:', entry);
42
+ },
43
+ })
44
+ })
45
+
46
+ // Use the logger
47
+ log.info("This is a test message");
48
+ log.withMetadata({ userId: "123" }).error("User not found");
49
+ ```
50
+
51
+ ## Configuration
52
+
53
+ ```typescript
54
+ interface NewRelicTransportConfig {
55
+ /**
56
+ * Whether the transport is enabled. Default is true.
57
+ */
58
+ enabled?: boolean;
59
+ /**
60
+ * The New Relic API key
61
+ */
62
+ apiKey: string;
63
+ /**
64
+ * The New Relic Log API endpoint
65
+ * @default https://log-api.newrelic.com/log/v1
66
+ */
67
+ endpoint?: string;
68
+ /**
69
+ * Optional callback for error handling
70
+ */
71
+ onError?: (err: Error) => void;
72
+ /**
73
+ * Optional callback for debugging log entries
74
+ * Called with the validated entry before it is sent
75
+ */
76
+ onDebug?: (entry: Record<string, any>) => void;
77
+ /**
78
+ * Whether to use gzip compression
79
+ * @default true
80
+ */
81
+ useCompression?: boolean;
82
+ /**
83
+ * Number of retry attempts before giving up
84
+ * @default 3
85
+ */
86
+ maxRetries?: number;
87
+ /**
88
+ * Base delay between retries in milliseconds.
89
+ * The actual delay will use exponential backoff with jitter.
90
+ * @default 1000
91
+ */
92
+ retryDelay?: number;
93
+ /**
94
+ * Whether to respect rate limiting by waiting when a 429 response is received
95
+ * @default true
96
+ */
97
+ respectRateLimit?: boolean;
98
+ }
99
+ ```
100
+
101
+ ## Documentation
102
+
103
+ For more details, visit [https://loglayer.dev/transports/new-relic](https://loglayer.dev/transports/new-relic)
package/dist/index.cjs ADDED
@@ -0,0 +1,239 @@
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;
7
+ var ValidationError = class extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "ValidationError";
11
+ }
12
+ };
13
+ var RateLimitError = class extends Error {
14
+ constructor(message, retryAfter) {
15
+ super(message);
16
+ this.retryAfter = retryAfter;
17
+ this.name = "RateLimitError";
18
+ }
19
+ };
20
+ 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;
40
+ }
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: (/* @__PURE__ */ new Date()).getTime(),
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
+ }
151
+ };
152
+ 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;
173
+ }
174
+ 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;
235
+ }
236
+
237
+
238
+ exports.NewRelicTransport = NewRelicTransport;
239
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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 { LogLayerTransportParams } from \"@loglayer/transport\";\nimport type { LoggerlessTransportConfig } 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: new Date().getTime(),\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"]}
@@ -0,0 +1,104 @@
1
+ import { LoggerlessTransportConfig, LoggerlessTransport, LogLayerTransportParams } from '@loglayer/transport';
2
+
3
+ /**
4
+ * Configuration options for the New Relic transport.
5
+ */
6
+ interface NewRelicTransportConfig extends LoggerlessTransportConfig {
7
+ /**
8
+ * The New Relic API key
9
+ */
10
+ apiKey: string;
11
+ /**
12
+ * The New Relic Log API endpoint
13
+ * @default https://log-api.newrelic.com/log/v1
14
+ */
15
+ endpoint?: string;
16
+ /**
17
+ * Optional callback for error handling
18
+ */
19
+ onError?: (err: Error) => void;
20
+ /**
21
+ * Optional callback for debugging log entries before they are sent
22
+ */
23
+ onDebug?: (entry: Record<string, any>) => void;
24
+ /**
25
+ * Whether to use gzip compression
26
+ * @default true
27
+ */
28
+ useCompression?: boolean;
29
+ /**
30
+ * Number of retry attempts before giving up
31
+ * @default 3
32
+ */
33
+ maxRetries?: number;
34
+ /**
35
+ * Base delay between retries in milliseconds
36
+ * @default 1000
37
+ */
38
+ retryDelay?: number;
39
+ /**
40
+ * Whether to respect rate limiting by waiting when a 429 response is received
41
+ * @default true
42
+ */
43
+ respectRateLimit?: boolean;
44
+ }
45
+ /**
46
+ * NewRelicTransport is responsible for sending logs to New Relic's Log API.
47
+ * It handles validation, compression, retries, and rate limiting according to New Relic's specifications.
48
+ *
49
+ * Features:
50
+ * - Validates payload size (max 1MB)
51
+ * - Validates number of attributes (max 255)
52
+ * - Validates attribute name length (max 255 characters)
53
+ * - Truncates attribute values longer than 4094 characters
54
+ * - Supports gzip compression
55
+ * - Handles rate limiting with configurable behavior
56
+ * - Implements retry logic with exponential backoff
57
+ */
58
+ declare class NewRelicTransport extends LoggerlessTransport {
59
+ private apiKey;
60
+ private endpoint;
61
+ private onError?;
62
+ private onDebug?;
63
+ private useCompression;
64
+ private maxRetries;
65
+ private retryDelay;
66
+ private respectRateLimit;
67
+ /**
68
+ * Creates a new instance of NewRelicTransport.
69
+ *
70
+ * @param config - Configuration options for the transport
71
+ * @param config.apiKey - New Relic API key for authentication
72
+ * @param config.endpoint - Optional custom endpoint URL (defaults to New Relic's Log API endpoint)
73
+ * @param config.onError - Optional error callback for handling errors
74
+ * @param config.onDebug - Optional callback for debugging log entries before they are sent
75
+ * @param config.useCompression - Whether to use gzip compression (defaults to true)
76
+ * @param config.maxRetries - Maximum number of retry attempts (defaults to 3)
77
+ * @param config.retryDelay - Base delay between retries in milliseconds (defaults to 1000)
78
+ * @param config.respectRateLimit - Whether to honor rate limiting headers (defaults to true)
79
+ */
80
+ constructor(config: NewRelicTransportConfig);
81
+ /**
82
+ * Processes and ships log entries to New Relic.
83
+ *
84
+ * This method:
85
+ * 1. Validates the message size
86
+ * 2. Creates and validates the log entry
87
+ * 3. Validates the final payload size
88
+ * 4. Asynchronously sends the log entry to New Relic
89
+ *
90
+ * The actual sending is done asynchronously in a fire-and-forget manner to maintain
91
+ * compatibility with the base transport class while still providing retry and error handling.
92
+ *
93
+ * @param params - Log parameters including level, messages, and metadata
94
+ * @param params.logLevel - The severity level of the log
95
+ * @param params.messages - Array of message strings to be joined
96
+ * @param params.data - Optional metadata to include with the log
97
+ * @param params.hasData - Whether metadata is present
98
+ * @returns The original messages array
99
+ * @throws {ValidationError} If the payload exceeds size limits or validation fails
100
+ */
101
+ shipToLogger({ logLevel, messages, data, hasData }: LogLayerTransportParams): any[];
102
+ }
103
+
104
+ export { NewRelicTransport, type NewRelicTransportConfig };
@@ -0,0 +1,104 @@
1
+ import { LoggerlessTransportConfig, LoggerlessTransport, LogLayerTransportParams } from '@loglayer/transport';
2
+
3
+ /**
4
+ * Configuration options for the New Relic transport.
5
+ */
6
+ interface NewRelicTransportConfig extends LoggerlessTransportConfig {
7
+ /**
8
+ * The New Relic API key
9
+ */
10
+ apiKey: string;
11
+ /**
12
+ * The New Relic Log API endpoint
13
+ * @default https://log-api.newrelic.com/log/v1
14
+ */
15
+ endpoint?: string;
16
+ /**
17
+ * Optional callback for error handling
18
+ */
19
+ onError?: (err: Error) => void;
20
+ /**
21
+ * Optional callback for debugging log entries before they are sent
22
+ */
23
+ onDebug?: (entry: Record<string, any>) => void;
24
+ /**
25
+ * Whether to use gzip compression
26
+ * @default true
27
+ */
28
+ useCompression?: boolean;
29
+ /**
30
+ * Number of retry attempts before giving up
31
+ * @default 3
32
+ */
33
+ maxRetries?: number;
34
+ /**
35
+ * Base delay between retries in milliseconds
36
+ * @default 1000
37
+ */
38
+ retryDelay?: number;
39
+ /**
40
+ * Whether to respect rate limiting by waiting when a 429 response is received
41
+ * @default true
42
+ */
43
+ respectRateLimit?: boolean;
44
+ }
45
+ /**
46
+ * NewRelicTransport is responsible for sending logs to New Relic's Log API.
47
+ * It handles validation, compression, retries, and rate limiting according to New Relic's specifications.
48
+ *
49
+ * Features:
50
+ * - Validates payload size (max 1MB)
51
+ * - Validates number of attributes (max 255)
52
+ * - Validates attribute name length (max 255 characters)
53
+ * - Truncates attribute values longer than 4094 characters
54
+ * - Supports gzip compression
55
+ * - Handles rate limiting with configurable behavior
56
+ * - Implements retry logic with exponential backoff
57
+ */
58
+ declare class NewRelicTransport extends LoggerlessTransport {
59
+ private apiKey;
60
+ private endpoint;
61
+ private onError?;
62
+ private onDebug?;
63
+ private useCompression;
64
+ private maxRetries;
65
+ private retryDelay;
66
+ private respectRateLimit;
67
+ /**
68
+ * Creates a new instance of NewRelicTransport.
69
+ *
70
+ * @param config - Configuration options for the transport
71
+ * @param config.apiKey - New Relic API key for authentication
72
+ * @param config.endpoint - Optional custom endpoint URL (defaults to New Relic's Log API endpoint)
73
+ * @param config.onError - Optional error callback for handling errors
74
+ * @param config.onDebug - Optional callback for debugging log entries before they are sent
75
+ * @param config.useCompression - Whether to use gzip compression (defaults to true)
76
+ * @param config.maxRetries - Maximum number of retry attempts (defaults to 3)
77
+ * @param config.retryDelay - Base delay between retries in milliseconds (defaults to 1000)
78
+ * @param config.respectRateLimit - Whether to honor rate limiting headers (defaults to true)
79
+ */
80
+ constructor(config: NewRelicTransportConfig);
81
+ /**
82
+ * Processes and ships log entries to New Relic.
83
+ *
84
+ * This method:
85
+ * 1. Validates the message size
86
+ * 2. Creates and validates the log entry
87
+ * 3. Validates the final payload size
88
+ * 4. Asynchronously sends the log entry to New Relic
89
+ *
90
+ * The actual sending is done asynchronously in a fire-and-forget manner to maintain
91
+ * compatibility with the base transport class while still providing retry and error handling.
92
+ *
93
+ * @param params - Log parameters including level, messages, and metadata
94
+ * @param params.logLevel - The severity level of the log
95
+ * @param params.messages - Array of message strings to be joined
96
+ * @param params.data - Optional metadata to include with the log
97
+ * @param params.hasData - Whether metadata is present
98
+ * @returns The original messages array
99
+ * @throws {ValidationError} If the payload exceeds size limits or validation fails
100
+ */
101
+ shipToLogger({ logLevel, messages, data, hasData }: LogLayerTransportParams): any[];
102
+ }
103
+
104
+ export { NewRelicTransport, type NewRelicTransportConfig };
package/dist/index.js ADDED
@@ -0,0 +1,239 @@
1
+ // src/NewRelicTransport.ts
2
+ import { LoggerlessTransport } from "@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;
7
+ var ValidationError = class extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "ValidationError";
11
+ }
12
+ };
13
+ var RateLimitError = class extends Error {
14
+ constructor(message, retryAfter) {
15
+ super(message);
16
+ this.retryAfter = retryAfter;
17
+ this.name = "RateLimitError";
18
+ }
19
+ };
20
+ 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;
40
+ }
41
+ var NewRelicTransport = class extends LoggerlessTransport {
42
+ apiKey;
43
+ endpoint;
44
+ onError;
45
+ onDebug;
46
+ useCompression;
47
+ maxRetries;
48
+ retryDelay;
49
+ respectRateLimit;
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 = config.endpoint ?? "https://log-api.newrelic.com/log/v1";
67
+ this.onError = config.onError;
68
+ this.onDebug = config.onDebug;
69
+ this.useCompression = config.useCompression ?? true;
70
+ this.maxRetries = config.maxRetries ?? 3;
71
+ this.retryDelay = config.retryDelay ?? 1e3;
72
+ this.respectRateLimit = 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: (/* @__PURE__ */ new Date()).getTime(),
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
+ }
151
+ };
152
+ 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;
173
+ }
174
+ 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;
235
+ }
236
+ export {
237
+ NewRelicTransport
238
+ };
239
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/NewRelicTransport.ts"],"sourcesContent":["import type { LogLayerTransportParams } from \"@loglayer/transport\";\nimport type { LoggerlessTransportConfig } 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: new Date().getTime(),\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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","names":[]}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@loglayer/transport-new-relic",
3
+ "description": "New Relic transport for loglayer.",
4
+ "version": "1.0.1",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "exports": {
9
+ "import": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "require": {
14
+ "types": "./dist/index.d.cts",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "types": "./dist/index.d.ts",
19
+ "license": "MIT",
20
+ "repository": "loglayer/loglayer.git",
21
+ "author": "Theo Gravity <theo@suteki.nu>",
22
+ "keywords": [
23
+ "logging",
24
+ "log",
25
+ "loglayer",
26
+ "new-relic",
27
+ "newrelic",
28
+ "relic",
29
+ "transport"
30
+ ],
31
+ "dependencies": {
32
+ "@loglayer/transport": "1.1.2"
33
+ },
34
+ "devDependencies": {
35
+ "dotenv": "16.4.7",
36
+ "hash-runner": "2.0.1",
37
+ "@types/node": "22.10.4",
38
+ "serialize-error": "11.0.3",
39
+ "tsx": "4.19.2",
40
+ "tsup": "8.3.5",
41
+ "typescript": "5.7.2",
42
+ "vitest": "2.1.8",
43
+ "loglayer": "5.0.7",
44
+ "@internal/tsconfig": "1.0.0"
45
+ },
46
+ "bugs": "https://github.com/loglayer/loglayer/issues",
47
+ "engines": {
48
+ "node": ">=18"
49
+ },
50
+ "files": [
51
+ "dist"
52
+ ],
53
+ "homepage": "https://loglayer.dev",
54
+ "scripts": {
55
+ "build": "tsup src/index.ts",
56
+ "test": "vitest --run",
57
+ "build:dev": "hash-runner",
58
+ "clean": "rm -rf .turbo node_modules dist",
59
+ "lint": "biome check --write --unsafe src && biome format src --write && biome lint src --fix",
60
+ "verify-types": "tsc --noEmit",
61
+ "livetest": "tsx src/__tests__/livetest.ts"
62
+ }
63
+ }