@loglayer/transport-new-relic 3.0.2 → 4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Theo Gravity
3
+ Copyright (c) 2026 Theo Gravity / [Disaresta](https://disaresta.com)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -6,13 +6,13 @@
6
6
 
7
7
  The New Relic transport for the [LogLayer](https://loglayer.dev) logging library.
8
8
 
9
- Ships logs to New Relic using their [Log API](https://docs.newrelic.com/docs/logs/log-api/introduction-log-api/). Features include:
9
+ Ships logs to New Relic using their [Log API](https://docs.newrelic.com/docs/logs/log-api/introduction-log-api/). Compatible with Node.js, Bun, and Deno. Features include:
10
10
  - Automatic gzip compression (configurable)
11
11
  - Retry mechanism with exponential backoff
12
- - Rate limiting support with configurable behavior
12
+ - Rate limiting support
13
+ - Batch sending with configurable size and timeout
13
14
  - Validation of New Relic's API constraints
14
- - Error handling callback
15
- - Configurable endpoints for different regions
15
+ - Error handling and debug callbacks
16
16
 
17
17
  ## Installation
18
18
 
@@ -32,12 +32,12 @@ const log = new LogLayer({
32
32
  transport: new NewRelicTransport({
33
33
  apiKey: "YOUR_NEW_RELIC_API_KEY",
34
34
  endpoint: "https://log-api.newrelic.com/log/v1", // optional, this is the default
35
- useCompression: true, // optional, defaults to true
35
+ compression: true, // optional, defaults to true
36
36
  maxRetries: 3, // optional, defaults to 3
37
37
  retryDelay: 1000, // optional, base delay in ms, defaults to 1000
38
38
  respectRateLimit: true, // optional, defaults to true
39
39
  onError: (err) => {
40
- console.error('Failed to send logs to New Relic:', err);
40
+ console.error('Failed to send logs:', err);
41
41
  },
42
42
  onDebug: (entry) => {
43
43
  console.log('Log entry being sent:', entry);
@@ -64,7 +64,7 @@ interface NewRelicTransportConfig {
64
64
  apiKey: string;
65
65
  /**
66
66
  * The New Relic Log API endpoint
67
- * @default https://log-api.newrelic.com/log/v1
67
+ * @default "https://log-api.newrelic.com/log/v1"
68
68
  */
69
69
  endpoint?: string;
70
70
  /**
@@ -73,22 +73,21 @@ interface NewRelicTransportConfig {
73
73
  onError?: (err: Error) => void;
74
74
  /**
75
75
  * Optional callback for debugging log entries
76
- * Called with the validated entry before it is sent
76
+ * Called with the entry before it is sent
77
77
  */
78
78
  onDebug?: (entry: Record<string, any>) => void;
79
79
  /**
80
80
  * Whether to use gzip compression
81
81
  * @default true
82
82
  */
83
- useCompression?: boolean;
83
+ compression?: boolean;
84
84
  /**
85
85
  * Number of retry attempts before giving up
86
86
  * @default 3
87
87
  */
88
88
  maxRetries?: number;
89
89
  /**
90
- * Base delay between retries in milliseconds.
91
- * The actual delay will use exponential backoff with jitter.
90
+ * Base delay between retries in milliseconds
92
91
  * @default 1000
93
92
  */
94
93
  retryDelay?: number;
@@ -102,4 +101,4 @@ interface NewRelicTransportConfig {
102
101
 
103
102
  ## Documentation
104
103
 
105
- For more details, visit [https://loglayer.dev/transports/new-relic](https://loglayer.dev/transports/new-relic)
104
+ For more details, visit [https://loglayer.dev/transports/new-relic](https://loglayer.dev/transports/new-relic)
package/dist/index.cjs CHANGED
@@ -1,14 +1,13 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- let _loglayer_transport = require("@loglayer/transport");
2
+ let _loglayer_transport_http = require("@loglayer/transport-http");
3
3
 
4
4
  //#region src/NewRelicTransport.ts
5
- const MAX_PAYLOAD_SIZE = 1e6;
6
5
  const MAX_ATTRIBUTES = 255;
7
6
  const MAX_ATTRIBUTE_NAME_LENGTH = 255;
8
7
  const MAX_ATTRIBUTE_VALUE_LENGTH = 4094;
9
8
  /**
10
9
  * Error thrown when log entry validation fails.
11
- * This includes payload size, attribute count, and attribute name length validations.
10
+ * This includes attribute count, attribute name length, and other New Relic API validations.
12
11
  */
13
12
  var ValidationError = class extends Error {
14
13
  constructor(message) {
@@ -17,222 +16,100 @@ var ValidationError = class extends Error {
17
16
  }
18
17
  };
19
18
  /**
20
- * Error thrown when New Relic's API rate limit is exceeded.
21
- * Contains the retry-after duration specified by the API.
22
- */
23
- var RateLimitError = class extends Error {
24
- constructor(message, retryAfter) {
25
- super(message);
26
- this.retryAfter = retryAfter;
27
- this.name = "RateLimitError";
28
- }
29
- };
30
- /**
31
- * Validates a log entry against New Relic's constraints.
19
+ * Validates attributes against New Relic's constraints.
32
20
  * - Checks number of attributes (max 255)
33
21
  * - Validates attribute name length (max 255 characters)
34
22
  * - Truncates attribute values longer than 4094 characters
35
23
  *
36
- * @param logEntry - The log entry to validate
37
- * @returns The validated (and potentially modified) log entry
24
+ * @param attributes - The attributes to validate
25
+ * @returns The validated (and potentially modified) attributes
38
26
  * @throws {ValidationError} If validation fails
39
27
  */
40
- function validateLogEntry(logEntry) {
41
- if (logEntry.attributes) {
42
- const attributeCount = Object.keys(logEntry.attributes).length;
43
- if (attributeCount > MAX_ATTRIBUTES) throw new ValidationError(`Log entry exceeds maximum number of attributes (${MAX_ATTRIBUTES}). Found: ${attributeCount}`);
44
- for (const [key, value] of Object.entries(logEntry.attributes)) {
45
- if (key.length > MAX_ATTRIBUTE_NAME_LENGTH) throw new ValidationError(`Attribute name '${key}' exceeds maximum length (${MAX_ATTRIBUTE_NAME_LENGTH}). Length: ${key.length}`);
46
- if (typeof value === "string" && value.length > MAX_ATTRIBUTE_VALUE_LENGTH) logEntry.attributes[key] = value.slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);
47
- }
28
+ function validateAttributes(attributes) {
29
+ const validated = {};
30
+ const attributeCount = Object.keys(attributes).length;
31
+ if (attributeCount > MAX_ATTRIBUTES) throw new ValidationError(`Log entry exceeds maximum number of attributes (${MAX_ATTRIBUTES}). Found: ${attributeCount}`);
32
+ for (const [key, value] of Object.entries(attributes)) {
33
+ if (key.length > MAX_ATTRIBUTE_NAME_LENGTH) throw new ValidationError(`Attribute name exceeds maximum length (${MAX_ATTRIBUTE_NAME_LENGTH}). Found: ${key.length}`);
34
+ if (typeof value === "string" && value.length > MAX_ATTRIBUTE_VALUE_LENGTH) validated[key] = value.slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);
35
+ else validated[key] = value;
48
36
  }
49
- return logEntry;
37
+ return validated;
38
+ }
39
+ /**
40
+ * Default payload template that formats log data for New Relic's Log API.
41
+ * Each entry includes timestamp, level, the log message, and optional attributes.
42
+ */
43
+ function defaultNewRelicPayload(params) {
44
+ const logEntry = {
45
+ timestamp: Date.now(),
46
+ level: params.logLevel,
47
+ log: params.message
48
+ };
49
+ if (params.data) logEntry.attributes = validateAttributes(params.data);
50
+ return JSON.stringify(logEntry);
50
51
  }
51
52
  /**
52
53
  * NewRelicTransport is responsible for sending logs to New Relic's Log API.
53
- * It handles validation, compression, retries, and rate limiting according to New Relic's specifications.
54
+ * It extends HttpTransport with New Relic-specific configuration, validation,
55
+ * and formatting.
54
56
  *
55
57
  * Features:
56
- * - Validates payload size (max 1MB)
57
- * - Validates number of attributes (max 255)
58
+ * - Validates attribute count (max 255)
58
59
  * - Validates attribute name length (max 255 characters)
59
60
  * - Truncates attribute values longer than 4094 characters
60
- * - Supports gzip compression
61
- * - Handles rate limiting with configurable behavior
62
- * - Implements retry logic with exponential backoff
61
+ * - Gzip compression by default (via HttpTransport)
62
+ * - Retry logic with exponential backoff (via HttpTransport)
63
+ * - Rate limiting support (via HttpTransport)
64
+ * - Batch sending support (via HttpTransport)
65
+ * - Error and debug callbacks
63
66
  */
64
- var NewRelicTransport = class extends _loglayer_transport.LoggerlessTransport {
65
- apiKey;
66
- endpoint;
67
- onError;
68
- onDebug;
69
- useCompression;
70
- maxRetries;
71
- retryDelay;
72
- respectRateLimit;
67
+ var NewRelicTransport = class extends _loglayer_transport_http.HttpTransport {
73
68
  /**
74
69
  * Creates a new instance of NewRelicTransport.
75
70
  *
76
71
  * @param config - Configuration options for the transport
77
- * @param config.apiKey - New Relic API key for authentication
78
- * @param config.endpoint - Optional custom endpoint URL (defaults to New Relic's Log API endpoint)
79
- * @param config.onError - Optional error callback for handling errors
80
- * @param config.onDebug - Optional callback for debugging log entries before they are sent
81
- * @param config.useCompression - Whether to use gzip compression (defaults to true)
82
- * @param config.maxRetries - Maximum number of retry attempts (defaults to 3)
83
- * @param config.retryDelay - Base delay between retries in milliseconds (defaults to 1000)
84
- * @param config.respectRateLimit - Whether to honor rate limiting headers (defaults to true)
85
72
  */
86
73
  constructor(config) {
87
- super(config);
88
- this.apiKey = config.apiKey;
89
- this.endpoint = config.endpoint ?? "https://log-api.newrelic.com/log/v1";
90
- this.onError = config.onError;
91
- this.onDebug = config.onDebug;
92
- this.useCompression = config.useCompression ?? true;
93
- this.maxRetries = config.maxRetries ?? 3;
94
- this.retryDelay = config.retryDelay ?? 1e3;
95
- this.respectRateLimit = config.respectRateLimit ?? true;
96
- }
97
- /**
98
- * Processes and ships log entries to New Relic.
99
- *
100
- * This method:
101
- * 1. Validates the message size
102
- * 2. Creates and validates the log entry
103
- * 3. Validates the final payload size
104
- * 4. Asynchronously sends the log entry to New Relic
105
- *
106
- * The actual sending is done asynchronously in a fire-and-forget manner to maintain
107
- * compatibility with the base transport class while still providing retry and error handling.
108
- *
109
- * @param params - Log parameters including level, messages, and metadata
110
- * @param params.logLevel - The severity level of the log
111
- * @param params.messages - Array of message strings to be joined
112
- * @param params.data - Optional metadata to include with the log
113
- * @param params.hasData - Whether metadata is present
114
- * @returns The original messages array
115
- * @throws {ValidationError} If the payload exceeds size limits or validation fails
116
- */
117
- shipToLogger({ logLevel, messages, data, hasData }) {
118
- try {
119
- const message = messages.join(" ");
120
- const messageBytes = new TextEncoder().encode(message).length;
121
- if (messageBytes > MAX_PAYLOAD_SIZE) throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${messageBytes} bytes`);
122
- const logEntry = {
123
- timestamp: Date.now(),
124
- level: logLevel,
125
- log: message
126
- };
127
- if (data && hasData) Object.assign(logEntry, { attributes: data });
128
- const validatedEntry = validateLogEntry(logEntry);
129
- if (this.onDebug) this.onDebug(validatedEntry);
130
- const payload = JSON.stringify([validatedEntry]);
131
- const payloadBytes = new TextEncoder().encode(payload).length;
132
- if (payloadBytes > MAX_PAYLOAD_SIZE) throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`);
133
- (async () => {
134
- try {
135
- await sendWithRetry(this.endpoint, this.apiKey, payload, this.useCompression, this.maxRetries, this.retryDelay, this.respectRateLimit);
136
- } catch (error) {
137
- if (this.onError) this.onError(error instanceof Error ? error : new Error(String(error)));
138
- if (error instanceof ValidationError) throw error;
139
- }
140
- })();
141
- } catch (error) {
142
- if (this.onError) this.onError(error instanceof Error ? error : new Error(String(error)));
143
- }
144
- return messages;
145
- }
146
- };
147
- /**
148
- * Compresses data using gzip compression.
149
- *
150
- * @param data - The string data to compress
151
- * @returns A promise that resolves to the compressed data as a Uint8Array
152
- */
153
- async function compressData(data) {
154
- const stream = new CompressionStream("gzip");
155
- const writer = stream.writable.getWriter();
156
- const encoder = new TextEncoder();
157
- const chunks = [];
158
- await writer.write(encoder.encode(data));
159
- await writer.close();
160
- const reader = stream.readable.getReader();
161
- while (true) {
162
- const { value, done } = await reader.read();
163
- if (done) break;
164
- chunks.push(value);
165
- }
166
- const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
167
- const result = new Uint8Array(totalLength);
168
- let offset = 0;
169
- for (const chunk of chunks) {
170
- result.set(chunk, offset);
171
- offset += chunk.length;
172
- }
173
- return result;
174
- }
175
- /**
176
- * Sends a log entry to New Relic with retry logic.
177
- * Handles rate limiting, compression, and error cases.
178
- *
179
- * @param endpoint - The New Relic API endpoint
180
- * @param apiKey - The New Relic API key
181
- * @param payload - The JSON payload to send
182
- * @param useCompression - Whether to use gzip compression
183
- * @param maxRetries - Maximum number of retry attempts
184
- * @param retryDelay - Base delay between retries in milliseconds
185
- * @param respectRateLimit - Whether to honor rate limiting headers
186
- * @returns A promise that resolves to the API response
187
- * @throws {ValidationError} If payload validation fails
188
- * @throws {RateLimitError} If rate limited and not respecting rate limits
189
- * @throws {Error} If the request fails after all retries
190
- */
191
- async function sendWithRetry(endpoint, apiKey, payload, useCompression, maxRetries, retryDelay, respectRateLimit = true) {
192
- const payloadBytes = new TextEncoder().encode(payload).length;
193
- if (payloadBytes > MAX_PAYLOAD_SIZE) throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`);
194
- let lastError;
195
- let compressedPayload;
196
- if (useCompression) {
197
- compressedPayload = await compressData(payload);
198
- if (compressedPayload.length > MAX_PAYLOAD_SIZE) throw new ValidationError(`Compressed payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${compressedPayload.length} bytes`);
199
- }
200
- const headers = {
201
- "Content-Type": "application/json",
202
- "Api-Key": apiKey
203
- };
204
- if (useCompression) headers["Content-Encoding"] = "gzip";
205
- for (let attempt = 0; attempt <= maxRetries; attempt++) try {
206
- const response = await fetch(endpoint, {
74
+ const { apiKey, endpoint, payloadTemplate, ...httpConfig } = config;
75
+ super({
76
+ ...httpConfig,
77
+ url: endpoint ?? "https://log-api.newrelic.com/log/v1",
207
78
  method: "POST",
208
- headers,
209
- body: useCompression ? compressedPayload : payload
79
+ headers: {
80
+ "Content-Type": "application/json",
81
+ "Api-Key": apiKey
82
+ },
83
+ payloadTemplate: payloadTemplate ?? defaultNewRelicPayload,
84
+ compression: httpConfig.compression ?? true,
85
+ maxRetries: httpConfig.maxRetries ?? 3,
86
+ retryDelay: httpConfig.retryDelay ?? 1e3,
87
+ respectRateLimit: httpConfig.respectRateLimit ?? true,
88
+ maxLogSize: httpConfig.maxLogSize ?? 1048576,
89
+ maxPayloadSize: httpConfig.maxPayloadSize ?? 1048576
210
90
  });
211
- if (response.status === 429) {
212
- const retryAfter = Number.parseInt(response.headers.get("Retry-After") || "60", 10);
213
- if (respectRateLimit) {
214
- await new Promise((resolve) => setTimeout(resolve, retryAfter * 1e3));
215
- attempt--;
216
- continue;
217
- }
218
- throw new RateLimitError(`Rate limit exceeded. Retry after ${retryAfter} seconds`, retryAfter);
219
- }
220
- if (!response.ok) throw new Error(`Failed to send logs to New Relic: ${response.statusText}`);
221
- return response;
222
- } catch (error) {
223
- lastError = error instanceof Error ? error : new Error(String(error));
224
- if (error instanceof ValidationError) throw error;
225
- if (!respectRateLimit && error instanceof RateLimitError) throw error;
226
- if (attempt === maxRetries) throw new Error(`Failed to send logs after ${maxRetries} retries: ${lastError.message}`);
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
91
  }
233
- throw lastError;
234
- }
92
+ };
235
93
 
236
94
  //#endregion
95
+ Object.defineProperty(exports, 'HttpTransportError', {
96
+ enumerable: true,
97
+ get: function () {
98
+ return _loglayer_transport_http.HttpTransportError;
99
+ }
100
+ });
101
+ Object.defineProperty(exports, 'LogSizeError', {
102
+ enumerable: true,
103
+ get: function () {
104
+ return _loglayer_transport_http.LogSizeError;
105
+ }
106
+ });
237
107
  exports.NewRelicTransport = NewRelicTransport;
108
+ Object.defineProperty(exports, 'RateLimitError', {
109
+ enumerable: true,
110
+ get: function () {
111
+ return _loglayer_transport_http.RateLimitError;
112
+ }
113
+ });
114
+ exports.ValidationError = ValidationError;
238
115
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["LoggerlessTransport"],"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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"}
1
+ {"version":3,"file":"index.cjs","names":["HttpTransport"],"sources":["../src/NewRelicTransport.ts"],"sourcesContent":["import type { HttpTransportConfig } from \"@loglayer/transport-http\";\nimport { HttpTransport } from \"@loglayer/transport-http\";\n\n// Constants defining New Relic's API limits\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 attribute count, attribute name length, and other New Relic API validations.\n */\nexport class ValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ValidationError\";\n }\n}\n\n/**\n * Configuration options for the New Relic transport.\n * Extends HttpTransportConfig with New Relic-specific options.\n */\nexport interface NewRelicTransportConfig extends Omit<HttpTransportConfig, \"url\" | \"headers\" | \"payloadTemplate\"> {\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 * Custom payload template function (optional, defaults to New Relic format).\n * Receives log level, message, and optional data (metadata).\n */\n payloadTemplate?: (params: { logLevel: string; message: string; data?: Record<string, any> }) => string;\n}\n\n/**\n * Validates attributes 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 attributes - The attributes to validate\n * @returns The validated (and potentially modified) attributes\n * @throws {ValidationError} If validation fails\n */\nfunction validateAttributes(attributes: Record<string, any>): Record<string, any> {\n const validated: Record<string, any> = {};\n\n // Check number of attributes\n const attributeCount = Object.keys(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(attributes)) {\n // Check attribute name length\n if (key.length > MAX_ATTRIBUTE_NAME_LENGTH) {\n throw new ValidationError(\n `Attribute name exceeds maximum length (${MAX_ATTRIBUTE_NAME_LENGTH}). Found: ${key.length}`,\n );\n }\n\n // Truncate string values that are too long\n if (typeof value === \"string\" && value.length > MAX_ATTRIBUTE_VALUE_LENGTH) {\n validated[key] = value.slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);\n } else {\n validated[key] = value;\n }\n }\n\n return validated;\n}\n\n/**\n * Default payload template that formats log data for New Relic's Log API.\n * Each entry includes timestamp, level, the log message, and optional attributes.\n */\nfunction defaultNewRelicPayload(params: { logLevel: string; message: string; data?: Record<string, any> }): string {\n const logEntry: Record<string, any> = {\n timestamp: Date.now(),\n level: params.logLevel,\n log: params.message,\n };\n\n if (params.data) {\n logEntry.attributes = validateAttributes(params.data);\n }\n\n return JSON.stringify(logEntry);\n}\n\n/**\n * NewRelicTransport is responsible for sending logs to New Relic's Log API.\n * It extends HttpTransport with New Relic-specific configuration, validation,\n * and formatting.\n *\n * Features:\n * - Validates attribute count (max 255)\n * - Validates attribute name length (max 255 characters)\n * - Truncates attribute values longer than 4094 characters\n * - Gzip compression by default (via HttpTransport)\n * - Retry logic with exponential backoff (via HttpTransport)\n * - Rate limiting support (via HttpTransport)\n * - Batch sending support (via HttpTransport)\n * - Error and debug callbacks\n */\nexport class NewRelicTransport extends HttpTransport {\n /**\n * Creates a new instance of NewRelicTransport.\n *\n * @param config - Configuration options for the transport\n */\n constructor(config: NewRelicTransportConfig) {\n const { apiKey, endpoint, payloadTemplate, ...httpConfig } = config;\n\n super({\n ...httpConfig,\n url: endpoint ?? \"https://log-api.newrelic.com/log/v1\",\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Api-Key\": apiKey,\n },\n payloadTemplate: payloadTemplate ?? defaultNewRelicPayload,\n // Defaults (only apply if not provided)\n compression: httpConfig.compression ?? true,\n maxRetries: httpConfig.maxRetries ?? 3,\n retryDelay: httpConfig.retryDelay ?? 1000,\n respectRateLimit: httpConfig.respectRateLimit ?? true,\n maxLogSize: httpConfig.maxLogSize ?? 1_048_576,\n maxPayloadSize: httpConfig.maxPayloadSize ?? 1_048_576,\n } as HttpTransportConfig);\n }\n}\n"],"mappings":";;;;AAIA,MAAM,iBAAiB;AACvB,MAAM,4BAA4B;AAClC,MAAM,6BAA6B;;;;;AAMnC,IAAa,kBAAb,cAAqC,MAAM;CACzC,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;;;;;;;;;;AAmChB,SAAS,mBAAmB,YAAsD;CAChF,MAAM,YAAiC,EAAE;CAGzC,MAAM,iBAAiB,OAAO,KAAK,WAAW,CAAC;AAC/C,KAAI,iBAAiB,eACnB,OAAM,IAAI,gBACR,mDAAmD,eAAe,YAAY,iBAC/E;AAIH,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,WAAW,EAAE;AAErD,MAAI,IAAI,SAAS,0BACf,OAAM,IAAI,gBACR,0CAA0C,0BAA0B,YAAY,IAAI,SACrF;AAIH,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,2BAC9C,WAAU,OAAO,MAAM,MAAM,GAAG,2BAA2B;MAE3D,WAAU,OAAO;;AAIrB,QAAO;;;;;;AAOT,SAAS,uBAAuB,QAAmF;CACjH,MAAM,WAAgC;EACpC,WAAW,KAAK,KAAK;EACrB,OAAO,OAAO;EACd,KAAK,OAAO;EACb;AAED,KAAI,OAAO,KACT,UAAS,aAAa,mBAAmB,OAAO,KAAK;AAGvD,QAAO,KAAK,UAAU,SAAS;;;;;;;;;;;;;;;;;AAkBjC,IAAa,oBAAb,cAAuCA,uCAAc;;;;;;CAMnD,YAAY,QAAiC;EAC3C,MAAM,EAAE,QAAQ,UAAU,iBAAiB,GAAG,eAAe;AAE7D,QAAM;GACJ,GAAG;GACH,KAAK,YAAY;GACjB,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,WAAW;IACZ;GACD,iBAAiB,mBAAmB;GAEpC,aAAa,WAAW,eAAe;GACvC,YAAY,WAAW,cAAc;GACrC,YAAY,WAAW,cAAc;GACrC,kBAAkB,WAAW,oBAAoB;GACjD,YAAY,WAAW,cAAc;GACrC,gBAAgB,WAAW,kBAAkB;GAC9C,CAAwB"}
package/dist/index.d.cts CHANGED
@@ -1,111 +1,60 @@
1
- import { LogLayerTransportParams, LoggerlessTransport, LoggerlessTransportConfig } from "@loglayer/transport";
1
+ import { HttpTransport, HttpTransportConfig, HttpTransportError, LogSizeError, RateLimitError } from "@loglayer/transport-http";
2
2
 
3
3
  //#region src/NewRelicTransport.d.ts
4
+ /**
5
+ * Error thrown when log entry validation fails.
6
+ * This includes attribute count, attribute name length, and other New Relic API validations.
7
+ */
8
+ declare class ValidationError extends Error {
9
+ constructor(message: string);
10
+ }
4
11
  /**
5
12
  * Configuration options for the New Relic transport.
13
+ * Extends HttpTransportConfig with New Relic-specific options.
6
14
  */
7
- interface NewRelicTransportConfig extends LoggerlessTransportConfig {
15
+ interface NewRelicTransportConfig extends Omit<HttpTransportConfig, "url" | "headers" | "payloadTemplate"> {
8
16
  /**
9
17
  * The New Relic API key
10
18
  */
11
19
  apiKey: string;
12
20
  /**
13
21
  * The New Relic Log API endpoint
14
- * @default https://log-api.newrelic.com/log/v1
22
+ * @default "https://log-api.newrelic.com/log/v1"
15
23
  */
16
24
  endpoint?: string;
17
25
  /**
18
- * Optional callback for error handling
19
- */
20
- onError?: (err: Error) => void;
21
- /**
22
- * Optional callback for debugging log entries before they are sent
23
- */
24
- onDebug?: (entry: Record<string, any>) => void;
25
- /**
26
- * Whether to use gzip compression
27
- * @default true
28
- */
29
- useCompression?: boolean;
30
- /**
31
- * Number of retry attempts before giving up
32
- * @default 3
26
+ * Custom payload template function (optional, defaults to New Relic format).
27
+ * Receives log level, message, and optional data (metadata).
33
28
  */
34
- maxRetries?: number;
35
- /**
36
- * Base delay between retries in milliseconds
37
- * @default 1000
38
- */
39
- retryDelay?: number;
40
- /**
41
- * Whether to respect rate limiting by waiting when a 429 response is received
42
- * @default true
43
- */
44
- respectRateLimit?: boolean;
29
+ payloadTemplate?: (params: {
30
+ logLevel: string;
31
+ message: string;
32
+ data?: Record<string, any>;
33
+ }) => string;
45
34
  }
46
35
  /**
47
36
  * NewRelicTransport is responsible for sending logs to New Relic's Log API.
48
- * It handles validation, compression, retries, and rate limiting according to New Relic's specifications.
37
+ * It extends HttpTransport with New Relic-specific configuration, validation,
38
+ * and formatting.
49
39
  *
50
40
  * Features:
51
- * - Validates payload size (max 1MB)
52
- * - Validates number of attributes (max 255)
41
+ * - Validates attribute count (max 255)
53
42
  * - Validates attribute name length (max 255 characters)
54
43
  * - Truncates attribute values longer than 4094 characters
55
- * - Supports gzip compression
56
- * - Handles rate limiting with configurable behavior
57
- * - Implements retry logic with exponential backoff
44
+ * - Gzip compression by default (via HttpTransport)
45
+ * - Retry logic with exponential backoff (via HttpTransport)
46
+ * - Rate limiting support (via HttpTransport)
47
+ * - Batch sending support (via HttpTransport)
48
+ * - Error and debug callbacks
58
49
  */
59
- declare class NewRelicTransport extends LoggerlessTransport {
60
- private apiKey;
61
- private endpoint;
62
- private onError?;
63
- private onDebug?;
64
- private useCompression;
65
- private maxRetries;
66
- private retryDelay;
67
- private respectRateLimit;
50
+ declare class NewRelicTransport extends HttpTransport {
68
51
  /**
69
52
  * Creates a new instance of NewRelicTransport.
70
53
  *
71
54
  * @param config - Configuration options for the transport
72
- * @param config.apiKey - New Relic API key for authentication
73
- * @param config.endpoint - Optional custom endpoint URL (defaults to New Relic's Log API endpoint)
74
- * @param config.onError - Optional error callback for handling errors
75
- * @param config.onDebug - Optional callback for debugging log entries before they are sent
76
- * @param config.useCompression - Whether to use gzip compression (defaults to true)
77
- * @param config.maxRetries - Maximum number of retry attempts (defaults to 3)
78
- * @param config.retryDelay - Base delay between retries in milliseconds (defaults to 1000)
79
- * @param config.respectRateLimit - Whether to honor rate limiting headers (defaults to true)
80
55
  */
81
56
  constructor(config: NewRelicTransportConfig);
82
- /**
83
- * Processes and ships log entries to New Relic.
84
- *
85
- * This method:
86
- * 1. Validates the message size
87
- * 2. Creates and validates the log entry
88
- * 3. Validates the final payload size
89
- * 4. Asynchronously sends the log entry to New Relic
90
- *
91
- * The actual sending is done asynchronously in a fire-and-forget manner to maintain
92
- * compatibility with the base transport class while still providing retry and error handling.
93
- *
94
- * @param params - Log parameters including level, messages, and metadata
95
- * @param params.logLevel - The severity level of the log
96
- * @param params.messages - Array of message strings to be joined
97
- * @param params.data - Optional metadata to include with the log
98
- * @param params.hasData - Whether metadata is present
99
- * @returns The original messages array
100
- * @throws {ValidationError} If the payload exceeds size limits or validation fails
101
- */
102
- shipToLogger({
103
- logLevel,
104
- messages,
105
- data,
106
- hasData
107
- }: LogLayerTransportParams): any[];
108
57
  }
109
58
  //#endregion
110
- export { NewRelicTransport, NewRelicTransportConfig };
59
+ export { HttpTransportError, LogSizeError, NewRelicTransport, type NewRelicTransportConfig, RateLimitError, ValidationError };
111
60
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.mts CHANGED
@@ -1,111 +1,60 @@
1
- import { LogLayerTransportParams, LoggerlessTransport, LoggerlessTransportConfig } from "@loglayer/transport";
1
+ import { HttpTransport, HttpTransportConfig, HttpTransportError, LogSizeError, RateLimitError } from "@loglayer/transport-http";
2
2
 
3
3
  //#region src/NewRelicTransport.d.ts
4
+ /**
5
+ * Error thrown when log entry validation fails.
6
+ * This includes attribute count, attribute name length, and other New Relic API validations.
7
+ */
8
+ declare class ValidationError extends Error {
9
+ constructor(message: string);
10
+ }
4
11
  /**
5
12
  * Configuration options for the New Relic transport.
13
+ * Extends HttpTransportConfig with New Relic-specific options.
6
14
  */
7
- interface NewRelicTransportConfig extends LoggerlessTransportConfig {
15
+ interface NewRelicTransportConfig extends Omit<HttpTransportConfig, "url" | "headers" | "payloadTemplate"> {
8
16
  /**
9
17
  * The New Relic API key
10
18
  */
11
19
  apiKey: string;
12
20
  /**
13
21
  * The New Relic Log API endpoint
14
- * @default https://log-api.newrelic.com/log/v1
22
+ * @default "https://log-api.newrelic.com/log/v1"
15
23
  */
16
24
  endpoint?: string;
17
25
  /**
18
- * Optional callback for error handling
19
- */
20
- onError?: (err: Error) => void;
21
- /**
22
- * Optional callback for debugging log entries before they are sent
23
- */
24
- onDebug?: (entry: Record<string, any>) => void;
25
- /**
26
- * Whether to use gzip compression
27
- * @default true
28
- */
29
- useCompression?: boolean;
30
- /**
31
- * Number of retry attempts before giving up
32
- * @default 3
26
+ * Custom payload template function (optional, defaults to New Relic format).
27
+ * Receives log level, message, and optional data (metadata).
33
28
  */
34
- maxRetries?: number;
35
- /**
36
- * Base delay between retries in milliseconds
37
- * @default 1000
38
- */
39
- retryDelay?: number;
40
- /**
41
- * Whether to respect rate limiting by waiting when a 429 response is received
42
- * @default true
43
- */
44
- respectRateLimit?: boolean;
29
+ payloadTemplate?: (params: {
30
+ logLevel: string;
31
+ message: string;
32
+ data?: Record<string, any>;
33
+ }) => string;
45
34
  }
46
35
  /**
47
36
  * NewRelicTransport is responsible for sending logs to New Relic's Log API.
48
- * It handles validation, compression, retries, and rate limiting according to New Relic's specifications.
37
+ * It extends HttpTransport with New Relic-specific configuration, validation,
38
+ * and formatting.
49
39
  *
50
40
  * Features:
51
- * - Validates payload size (max 1MB)
52
- * - Validates number of attributes (max 255)
41
+ * - Validates attribute count (max 255)
53
42
  * - Validates attribute name length (max 255 characters)
54
43
  * - Truncates attribute values longer than 4094 characters
55
- * - Supports gzip compression
56
- * - Handles rate limiting with configurable behavior
57
- * - Implements retry logic with exponential backoff
44
+ * - Gzip compression by default (via HttpTransport)
45
+ * - Retry logic with exponential backoff (via HttpTransport)
46
+ * - Rate limiting support (via HttpTransport)
47
+ * - Batch sending support (via HttpTransport)
48
+ * - Error and debug callbacks
58
49
  */
59
- declare class NewRelicTransport extends LoggerlessTransport {
60
- private apiKey;
61
- private endpoint;
62
- private onError?;
63
- private onDebug?;
64
- private useCompression;
65
- private maxRetries;
66
- private retryDelay;
67
- private respectRateLimit;
50
+ declare class NewRelicTransport extends HttpTransport {
68
51
  /**
69
52
  * Creates a new instance of NewRelicTransport.
70
53
  *
71
54
  * @param config - Configuration options for the transport
72
- * @param config.apiKey - New Relic API key for authentication
73
- * @param config.endpoint - Optional custom endpoint URL (defaults to New Relic's Log API endpoint)
74
- * @param config.onError - Optional error callback for handling errors
75
- * @param config.onDebug - Optional callback for debugging log entries before they are sent
76
- * @param config.useCompression - Whether to use gzip compression (defaults to true)
77
- * @param config.maxRetries - Maximum number of retry attempts (defaults to 3)
78
- * @param config.retryDelay - Base delay between retries in milliseconds (defaults to 1000)
79
- * @param config.respectRateLimit - Whether to honor rate limiting headers (defaults to true)
80
55
  */
81
56
  constructor(config: NewRelicTransportConfig);
82
- /**
83
- * Processes and ships log entries to New Relic.
84
- *
85
- * This method:
86
- * 1. Validates the message size
87
- * 2. Creates and validates the log entry
88
- * 3. Validates the final payload size
89
- * 4. Asynchronously sends the log entry to New Relic
90
- *
91
- * The actual sending is done asynchronously in a fire-and-forget manner to maintain
92
- * compatibility with the base transport class while still providing retry and error handling.
93
- *
94
- * @param params - Log parameters including level, messages, and metadata
95
- * @param params.logLevel - The severity level of the log
96
- * @param params.messages - Array of message strings to be joined
97
- * @param params.data - Optional metadata to include with the log
98
- * @param params.hasData - Whether metadata is present
99
- * @returns The original messages array
100
- * @throws {ValidationError} If the payload exceeds size limits or validation fails
101
- */
102
- shipToLogger({
103
- logLevel,
104
- messages,
105
- data,
106
- hasData
107
- }: LogLayerTransportParams): any[];
108
57
  }
109
58
  //#endregion
110
- export { NewRelicTransport, NewRelicTransportConfig };
59
+ export { HttpTransportError, LogSizeError, NewRelicTransport, type NewRelicTransportConfig, RateLimitError, ValidationError };
111
60
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,13 +1,12 @@
1
- import { LoggerlessTransport } from "@loglayer/transport";
1
+ import { HttpTransport, HttpTransportError, LogSizeError, RateLimitError } from "@loglayer/transport-http";
2
2
 
3
3
  //#region src/NewRelicTransport.ts
4
- const MAX_PAYLOAD_SIZE = 1e6;
5
4
  const MAX_ATTRIBUTES = 255;
6
5
  const MAX_ATTRIBUTE_NAME_LENGTH = 255;
7
6
  const MAX_ATTRIBUTE_VALUE_LENGTH = 4094;
8
7
  /**
9
8
  * Error thrown when log entry validation fails.
10
- * This includes payload size, attribute count, and attribute name length validations.
9
+ * This includes attribute count, attribute name length, and other New Relic API validations.
11
10
  */
12
11
  var ValidationError = class extends Error {
13
12
  constructor(message) {
@@ -16,222 +15,81 @@ var ValidationError = class extends Error {
16
15
  }
17
16
  };
18
17
  /**
19
- * Error thrown when New Relic's API rate limit is exceeded.
20
- * Contains the retry-after duration specified by the API.
21
- */
22
- var RateLimitError = class extends Error {
23
- constructor(message, retryAfter) {
24
- super(message);
25
- this.retryAfter = retryAfter;
26
- this.name = "RateLimitError";
27
- }
28
- };
29
- /**
30
- * Validates a log entry against New Relic's constraints.
18
+ * Validates attributes against New Relic's constraints.
31
19
  * - Checks number of attributes (max 255)
32
20
  * - Validates attribute name length (max 255 characters)
33
21
  * - Truncates attribute values longer than 4094 characters
34
22
  *
35
- * @param logEntry - The log entry to validate
36
- * @returns The validated (and potentially modified) log entry
23
+ * @param attributes - The attributes to validate
24
+ * @returns The validated (and potentially modified) attributes
37
25
  * @throws {ValidationError} If validation fails
38
26
  */
39
- function validateLogEntry(logEntry) {
40
- if (logEntry.attributes) {
41
- const attributeCount = Object.keys(logEntry.attributes).length;
42
- if (attributeCount > MAX_ATTRIBUTES) throw new ValidationError(`Log entry exceeds maximum number of attributes (${MAX_ATTRIBUTES}). Found: ${attributeCount}`);
43
- for (const [key, value] of Object.entries(logEntry.attributes)) {
44
- if (key.length > MAX_ATTRIBUTE_NAME_LENGTH) throw new ValidationError(`Attribute name '${key}' exceeds maximum length (${MAX_ATTRIBUTE_NAME_LENGTH}). Length: ${key.length}`);
45
- if (typeof value === "string" && value.length > MAX_ATTRIBUTE_VALUE_LENGTH) logEntry.attributes[key] = value.slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);
46
- }
27
+ function validateAttributes(attributes) {
28
+ const validated = {};
29
+ const attributeCount = Object.keys(attributes).length;
30
+ if (attributeCount > MAX_ATTRIBUTES) throw new ValidationError(`Log entry exceeds maximum number of attributes (${MAX_ATTRIBUTES}). Found: ${attributeCount}`);
31
+ for (const [key, value] of Object.entries(attributes)) {
32
+ if (key.length > MAX_ATTRIBUTE_NAME_LENGTH) throw new ValidationError(`Attribute name exceeds maximum length (${MAX_ATTRIBUTE_NAME_LENGTH}). Found: ${key.length}`);
33
+ if (typeof value === "string" && value.length > MAX_ATTRIBUTE_VALUE_LENGTH) validated[key] = value.slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);
34
+ else validated[key] = value;
47
35
  }
48
- return logEntry;
36
+ return validated;
37
+ }
38
+ /**
39
+ * Default payload template that formats log data for New Relic's Log API.
40
+ * Each entry includes timestamp, level, the log message, and optional attributes.
41
+ */
42
+ function defaultNewRelicPayload(params) {
43
+ const logEntry = {
44
+ timestamp: Date.now(),
45
+ level: params.logLevel,
46
+ log: params.message
47
+ };
48
+ if (params.data) logEntry.attributes = validateAttributes(params.data);
49
+ return JSON.stringify(logEntry);
49
50
  }
50
51
  /**
51
52
  * NewRelicTransport is responsible for sending logs to New Relic's Log API.
52
- * It handles validation, compression, retries, and rate limiting according to New Relic's specifications.
53
+ * It extends HttpTransport with New Relic-specific configuration, validation,
54
+ * and formatting.
53
55
  *
54
56
  * Features:
55
- * - Validates payload size (max 1MB)
56
- * - Validates number of attributes (max 255)
57
+ * - Validates attribute count (max 255)
57
58
  * - Validates attribute name length (max 255 characters)
58
59
  * - Truncates attribute values longer than 4094 characters
59
- * - Supports gzip compression
60
- * - Handles rate limiting with configurable behavior
61
- * - Implements retry logic with exponential backoff
60
+ * - Gzip compression by default (via HttpTransport)
61
+ * - Retry logic with exponential backoff (via HttpTransport)
62
+ * - Rate limiting support (via HttpTransport)
63
+ * - Batch sending support (via HttpTransport)
64
+ * - Error and debug callbacks
62
65
  */
63
- var NewRelicTransport = class extends LoggerlessTransport {
64
- apiKey;
65
- endpoint;
66
- onError;
67
- onDebug;
68
- useCompression;
69
- maxRetries;
70
- retryDelay;
71
- respectRateLimit;
66
+ var NewRelicTransport = class extends HttpTransport {
72
67
  /**
73
68
  * Creates a new instance of NewRelicTransport.
74
69
  *
75
70
  * @param config - Configuration options for the transport
76
- * @param config.apiKey - New Relic API key for authentication
77
- * @param config.endpoint - Optional custom endpoint URL (defaults to New Relic's Log API endpoint)
78
- * @param config.onError - Optional error callback for handling errors
79
- * @param config.onDebug - Optional callback for debugging log entries before they are sent
80
- * @param config.useCompression - Whether to use gzip compression (defaults to true)
81
- * @param config.maxRetries - Maximum number of retry attempts (defaults to 3)
82
- * @param config.retryDelay - Base delay between retries in milliseconds (defaults to 1000)
83
- * @param config.respectRateLimit - Whether to honor rate limiting headers (defaults to true)
84
71
  */
85
72
  constructor(config) {
86
- super(config);
87
- this.apiKey = config.apiKey;
88
- this.endpoint = config.endpoint ?? "https://log-api.newrelic.com/log/v1";
89
- this.onError = config.onError;
90
- this.onDebug = config.onDebug;
91
- this.useCompression = config.useCompression ?? true;
92
- this.maxRetries = config.maxRetries ?? 3;
93
- this.retryDelay = config.retryDelay ?? 1e3;
94
- this.respectRateLimit = config.respectRateLimit ?? true;
95
- }
96
- /**
97
- * Processes and ships log entries to New Relic.
98
- *
99
- * This method:
100
- * 1. Validates the message size
101
- * 2. Creates and validates the log entry
102
- * 3. Validates the final payload size
103
- * 4. Asynchronously sends the log entry to New Relic
104
- *
105
- * The actual sending is done asynchronously in a fire-and-forget manner to maintain
106
- * compatibility with the base transport class while still providing retry and error handling.
107
- *
108
- * @param params - Log parameters including level, messages, and metadata
109
- * @param params.logLevel - The severity level of the log
110
- * @param params.messages - Array of message strings to be joined
111
- * @param params.data - Optional metadata to include with the log
112
- * @param params.hasData - Whether metadata is present
113
- * @returns The original messages array
114
- * @throws {ValidationError} If the payload exceeds size limits or validation fails
115
- */
116
- shipToLogger({ logLevel, messages, data, hasData }) {
117
- try {
118
- const message = messages.join(" ");
119
- const messageBytes = new TextEncoder().encode(message).length;
120
- if (messageBytes > MAX_PAYLOAD_SIZE) throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${messageBytes} bytes`);
121
- const logEntry = {
122
- timestamp: Date.now(),
123
- level: logLevel,
124
- log: message
125
- };
126
- if (data && hasData) Object.assign(logEntry, { attributes: data });
127
- const validatedEntry = validateLogEntry(logEntry);
128
- if (this.onDebug) this.onDebug(validatedEntry);
129
- const payload = JSON.stringify([validatedEntry]);
130
- const payloadBytes = new TextEncoder().encode(payload).length;
131
- if (payloadBytes > MAX_PAYLOAD_SIZE) throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`);
132
- (async () => {
133
- try {
134
- await sendWithRetry(this.endpoint, this.apiKey, payload, this.useCompression, this.maxRetries, this.retryDelay, this.respectRateLimit);
135
- } catch (error) {
136
- if (this.onError) this.onError(error instanceof Error ? error : new Error(String(error)));
137
- if (error instanceof ValidationError) throw error;
138
- }
139
- })();
140
- } catch (error) {
141
- if (this.onError) this.onError(error instanceof Error ? error : new Error(String(error)));
142
- }
143
- return messages;
144
- }
145
- };
146
- /**
147
- * Compresses data using gzip compression.
148
- *
149
- * @param data - The string data to compress
150
- * @returns A promise that resolves to the compressed data as a Uint8Array
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
- /**
175
- * Sends a log entry to New Relic with retry logic.
176
- * Handles rate limiting, compression, and error cases.
177
- *
178
- * @param endpoint - The New Relic API endpoint
179
- * @param apiKey - The New Relic API key
180
- * @param payload - The JSON payload to send
181
- * @param useCompression - Whether to use gzip compression
182
- * @param maxRetries - Maximum number of retry attempts
183
- * @param retryDelay - Base delay between retries in milliseconds
184
- * @param respectRateLimit - Whether to honor rate limiting headers
185
- * @returns A promise that resolves to the API response
186
- * @throws {ValidationError} If payload validation fails
187
- * @throws {RateLimitError} If rate limited and not respecting rate limits
188
- * @throws {Error} If the request fails after all retries
189
- */
190
- async function sendWithRetry(endpoint, apiKey, payload, useCompression, maxRetries, retryDelay, respectRateLimit = true) {
191
- const payloadBytes = new TextEncoder().encode(payload).length;
192
- if (payloadBytes > MAX_PAYLOAD_SIZE) throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`);
193
- let lastError;
194
- let compressedPayload;
195
- if (useCompression) {
196
- compressedPayload = await compressData(payload);
197
- if (compressedPayload.length > MAX_PAYLOAD_SIZE) throw new ValidationError(`Compressed payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${compressedPayload.length} bytes`);
198
- }
199
- const headers = {
200
- "Content-Type": "application/json",
201
- "Api-Key": apiKey
202
- };
203
- if (useCompression) headers["Content-Encoding"] = "gzip";
204
- for (let attempt = 0; attempt <= maxRetries; attempt++) try {
205
- const response = await fetch(endpoint, {
73
+ const { apiKey, endpoint, payloadTemplate, ...httpConfig } = config;
74
+ super({
75
+ ...httpConfig,
76
+ url: endpoint ?? "https://log-api.newrelic.com/log/v1",
206
77
  method: "POST",
207
- headers,
208
- body: useCompression ? compressedPayload : payload
78
+ headers: {
79
+ "Content-Type": "application/json",
80
+ "Api-Key": apiKey
81
+ },
82
+ payloadTemplate: payloadTemplate ?? defaultNewRelicPayload,
83
+ compression: httpConfig.compression ?? true,
84
+ maxRetries: httpConfig.maxRetries ?? 3,
85
+ retryDelay: httpConfig.retryDelay ?? 1e3,
86
+ respectRateLimit: httpConfig.respectRateLimit ?? true,
87
+ maxLogSize: httpConfig.maxLogSize ?? 1048576,
88
+ maxPayloadSize: httpConfig.maxPayloadSize ?? 1048576
209
89
  });
210
- if (response.status === 429) {
211
- const retryAfter = Number.parseInt(response.headers.get("Retry-After") || "60", 10);
212
- if (respectRateLimit) {
213
- await new Promise((resolve) => setTimeout(resolve, retryAfter * 1e3));
214
- attempt--;
215
- continue;
216
- }
217
- throw new RateLimitError(`Rate limit exceeded. Retry after ${retryAfter} seconds`, retryAfter);
218
- }
219
- if (!response.ok) throw new Error(`Failed to send logs to New Relic: ${response.statusText}`);
220
- return response;
221
- } catch (error) {
222
- lastError = error instanceof Error ? error : new Error(String(error));
223
- if (error instanceof ValidationError) throw error;
224
- if (!respectRateLimit && error instanceof RateLimitError) throw error;
225
- if (attempt === maxRetries) throw new Error(`Failed to send logs after ${maxRetries} retries: ${lastError.message}`);
226
- if (!(error instanceof RateLimitError)) {
227
- const jitter = Math.random() * 200;
228
- const delay = retryDelay * 2 ** attempt + jitter;
229
- await new Promise((resolve) => setTimeout(resolve, delay));
230
- }
231
90
  }
232
- throw lastError;
233
- }
91
+ };
234
92
 
235
93
  //#endregion
236
- export { NewRelicTransport };
94
+ export { HttpTransportError, LogSizeError, NewRelicTransport, RateLimitError, ValidationError };
237
95
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"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":";;;AAIA,MAAM,mBAAmB;AACzB,MAAM,iBAAiB;AACvB,MAAM,4BAA4B;AAClC,MAAM,6BAA6B;;;;;AAMnC,IAAM,kBAAN,cAA8B,MAAM;CAClC,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;;;;AAQhB,IAAM,iBAAN,cAA6B,MAAM;CACjC,YACE,SACA,AAAO,YACP;AACA,QAAM,QAAQ;EAFP;AAGP,OAAK,OAAO;;;;;;;;;;;;;AAyDhB,SAAS,iBAAiB,UAA+B;AACvD,KAAI,SAAS,YAAY;EAEvB,MAAM,iBAAiB,OAAO,KAAK,SAAS,WAAW,CAAC;AACxD,MAAI,iBAAiB,eACnB,OAAM,IAAI,gBACR,mDAAmD,eAAe,YAAY,iBAC/E;AAIH,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,WAAW,EAAE;AAE9D,OAAI,IAAI,SAAS,0BACf,OAAM,IAAI,gBACR,mBAAmB,IAAI,4BAA4B,0BAA0B,aAAa,IAAI,SAC/F;AAIH,OAAI,OAAO,UAAU,YAAY,MAAM,SAAS,2BAE9C,UAAS,WAAW,OAAO,MAAM,MAAM,GAAG,2BAA2B;;;AAK3E,QAAO;;;;;;;;;;;;;;;AAgBT,IAAa,oBAAb,cAAuC,oBAAoB;CACzD,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;;;;;;;;;;;;;;CAeR,YAAY,QAAiC;AAC3C,QAAM,OAAO;AAEb,OAAK,SAAS,OAAO;AACrB,OAAK,WAAW,OAAO,YAAY;AACnC,OAAK,UAAU,OAAO;AACtB,OAAK,UAAU,OAAO;AACtB,OAAK,iBAAiB,OAAO,kBAAkB;AAC/C,OAAK,aAAa,OAAO,cAAc;AACvC,OAAK,aAAa,OAAO,cAAc;AACvC,OAAK,mBAAmB,OAAO,oBAAoB;;;;;;;;;;;;;;;;;;;;;;CAuBrD,aAAa,EAAE,UAAU,UAAU,MAAM,WAA2C;AAClF,MAAI;GAEF,MAAM,UAAU,SAAS,KAAK,IAAI;GAClC,MAAM,eAAe,IAAI,aAAa,CAAC,OAAO,QAAQ,CAAC;AACvD,OAAI,eAAe,iBACjB,OAAM,IAAI,gBACR,mCAAmC,iBAAiB,gBAAgB,aAAa,QAClF;GAGH,MAAM,WAAgC;IACpC,WAAW,KAAK,KAAK;IACrB,OAAO;IACP,KAAK;IACN;AAED,OAAI,QAAQ,QACV,QAAO,OAAO,UAAU,EACtB,YAAY,MACb,CAAC;GAGJ,MAAM,iBAAiB,iBAAiB,SAAS;AAGjD,OAAI,KAAK,QACP,MAAK,QAAQ,eAAe;GAI9B,MAAM,UAAU,KAAK,UAAU,CAAC,eAAe,CAAC;GAChD,MAAM,eAAe,IAAI,aAAa,CAAC,OAAO,QAAQ,CAAC;AACvD,OAAI,eAAe,iBACjB,OAAM,IAAI,gBACR,mCAAmC,iBAAiB,gBAAgB,aAAa,QAClF;AAIH,IAAC,YAAY;AACX,QAAI;AACF,WAAM,cACJ,KAAK,UACL,KAAK,QACL,SACA,KAAK,gBACL,KAAK,YACL,KAAK,YACL,KAAK,iBACN;aACM,OAAO;AACd,SAAI,KAAK,QACP,MAAK,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC,CAAC;AAGzE,SAAI,iBAAiB,gBACnB,OAAM;;OAGR;WACG,OAAO;AACd,OAAI,KAAK,QACP,MAAK,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC,CAAC;;AAI3E,SAAO;;;;;;;;;AAUX,eAAe,aAAa,MAAmC;CAC7D,MAAM,SAAS,IAAI,kBAAkB,OAAO;CAC5C,MAAM,SAAS,OAAO,SAAS,WAAW;CAC1C,MAAM,UAAU,IAAI,aAAa;CACjC,MAAM,SAAuB,EAAE;AAE/B,OAAM,OAAO,MAAM,QAAQ,OAAO,KAAK,CAAC;AACxC,OAAM,OAAO,OAAO;CAEpB,MAAM,SAAS,OAAO,SAAS,WAAW;AAE1C,QAAO,MAAM;EACX,MAAM,EAAE,OAAO,SAAS,MAAM,OAAO,MAAM;AAC3C,MAAI,KAAM;AACV,SAAO,KAAK,MAAM;;CAIpB,MAAM,cAAc,OAAO,QAAQ,KAAK,UAAU,MAAM,MAAM,QAAQ,EAAE;CACxE,MAAM,SAAS,IAAI,WAAW,YAAY;CAC1C,IAAI,SAAS;AACb,MAAK,MAAM,SAAS,QAAQ;AAC1B,SAAO,IAAI,OAAO,OAAO;AACzB,YAAU,MAAM;;AAGlB,QAAO;;;;;;;;;;;;;;;;;;AAmBT,eAAe,cACb,UACA,QACA,SACA,gBACA,YACA,YACA,mBAAmB,MACA;CAEnB,MAAM,eAAe,IAAI,aAAa,CAAC,OAAO,QAAQ,CAAC;AACvD,KAAI,eAAe,iBACjB,OAAM,IAAI,gBAAgB,mCAAmC,iBAAiB,gBAAgB,aAAa,QAAQ;CAGrH,IAAI;CACJ,IAAI;AAEJ,KAAI,gBAAgB;AAClB,sBAAoB,MAAM,aAAa,QAAQ;AAE/C,MAAI,kBAAkB,SAAS,iBAC7B,OAAM,IAAI,gBACR,8CAA8C,iBAAiB,gBAAgB,kBAAkB,OAAO,QACzG;;CAIL,MAAM,UAAkC;EACtC,gBAAgB;EAChB,WAAW;EACZ;AAED,KAAI,eACF,SAAQ,sBAAsB;AAGhC,MAAK,IAAI,UAAU,GAAG,WAAW,YAAY,UAC3C,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,UAAU;GACrC,QAAQ;GACR;GACA,MAAM,iBAAiB,oBAAoB;GAC5C,CAAC;AAEF,MAAI,SAAS,WAAW,KAAK;GAC3B,MAAM,aAAa,OAAO,SAAS,SAAS,QAAQ,IAAI,cAAc,IAAI,MAAM,GAAG;AACnF,OAAI,kBAAkB;AAEpB,UAAM,IAAI,SAAS,YAAY,WAAW,SAAS,aAAa,IAAK,CAAC;AAEtE;AACA;;AAGF,SAAM,IAAI,eAAe,oCAAoC,WAAW,WAAW,WAAW;;AAGhG,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,qCAAqC,SAAS,aAAa;AAG7E,SAAO;UACA,OAAO;AACd,cAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AAGrE,MAAI,iBAAiB,gBACnB,OAAM;AAIR,MAAI,CAAC,oBAAoB,iBAAiB,eACxC,OAAM;AAGR,MAAI,YAAY,WACd,OAAM,IAAI,MAAM,6BAA6B,WAAW,YAAY,UAAU,UAAU;AAI1F,MAAI,EAAE,iBAAiB,iBAAiB;GACtC,MAAM,SAAS,KAAK,QAAQ,GAAG;GAC/B,MAAM,QAAQ,aAAa,KAAK,UAAU;AAC1C,SAAM,IAAI,SAAS,YAAY,WAAW,SAAS,MAAM,CAAC;;;AAKhE,OAAM"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/NewRelicTransport.ts"],"sourcesContent":["import type { HttpTransportConfig } from \"@loglayer/transport-http\";\nimport { HttpTransport } from \"@loglayer/transport-http\";\n\n// Constants defining New Relic's API limits\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 attribute count, attribute name length, and other New Relic API validations.\n */\nexport class ValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ValidationError\";\n }\n}\n\n/**\n * Configuration options for the New Relic transport.\n * Extends HttpTransportConfig with New Relic-specific options.\n */\nexport interface NewRelicTransportConfig extends Omit<HttpTransportConfig, \"url\" | \"headers\" | \"payloadTemplate\"> {\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 * Custom payload template function (optional, defaults to New Relic format).\n * Receives log level, message, and optional data (metadata).\n */\n payloadTemplate?: (params: { logLevel: string; message: string; data?: Record<string, any> }) => string;\n}\n\n/**\n * Validates attributes 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 attributes - The attributes to validate\n * @returns The validated (and potentially modified) attributes\n * @throws {ValidationError} If validation fails\n */\nfunction validateAttributes(attributes: Record<string, any>): Record<string, any> {\n const validated: Record<string, any> = {};\n\n // Check number of attributes\n const attributeCount = Object.keys(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(attributes)) {\n // Check attribute name length\n if (key.length > MAX_ATTRIBUTE_NAME_LENGTH) {\n throw new ValidationError(\n `Attribute name exceeds maximum length (${MAX_ATTRIBUTE_NAME_LENGTH}). Found: ${key.length}`,\n );\n }\n\n // Truncate string values that are too long\n if (typeof value === \"string\" && value.length > MAX_ATTRIBUTE_VALUE_LENGTH) {\n validated[key] = value.slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);\n } else {\n validated[key] = value;\n }\n }\n\n return validated;\n}\n\n/**\n * Default payload template that formats log data for New Relic's Log API.\n * Each entry includes timestamp, level, the log message, and optional attributes.\n */\nfunction defaultNewRelicPayload(params: { logLevel: string; message: string; data?: Record<string, any> }): string {\n const logEntry: Record<string, any> = {\n timestamp: Date.now(),\n level: params.logLevel,\n log: params.message,\n };\n\n if (params.data) {\n logEntry.attributes = validateAttributes(params.data);\n }\n\n return JSON.stringify(logEntry);\n}\n\n/**\n * NewRelicTransport is responsible for sending logs to New Relic's Log API.\n * It extends HttpTransport with New Relic-specific configuration, validation,\n * and formatting.\n *\n * Features:\n * - Validates attribute count (max 255)\n * - Validates attribute name length (max 255 characters)\n * - Truncates attribute values longer than 4094 characters\n * - Gzip compression by default (via HttpTransport)\n * - Retry logic with exponential backoff (via HttpTransport)\n * - Rate limiting support (via HttpTransport)\n * - Batch sending support (via HttpTransport)\n * - Error and debug callbacks\n */\nexport class NewRelicTransport extends HttpTransport {\n /**\n * Creates a new instance of NewRelicTransport.\n *\n * @param config - Configuration options for the transport\n */\n constructor(config: NewRelicTransportConfig) {\n const { apiKey, endpoint, payloadTemplate, ...httpConfig } = config;\n\n super({\n ...httpConfig,\n url: endpoint ?? \"https://log-api.newrelic.com/log/v1\",\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Api-Key\": apiKey,\n },\n payloadTemplate: payloadTemplate ?? defaultNewRelicPayload,\n // Defaults (only apply if not provided)\n compression: httpConfig.compression ?? true,\n maxRetries: httpConfig.maxRetries ?? 3,\n retryDelay: httpConfig.retryDelay ?? 1000,\n respectRateLimit: httpConfig.respectRateLimit ?? true,\n maxLogSize: httpConfig.maxLogSize ?? 1_048_576,\n maxPayloadSize: httpConfig.maxPayloadSize ?? 1_048_576,\n } as HttpTransportConfig);\n }\n}\n"],"mappings":";;;AAIA,MAAM,iBAAiB;AACvB,MAAM,4BAA4B;AAClC,MAAM,6BAA6B;;;;;AAMnC,IAAa,kBAAb,cAAqC,MAAM;CACzC,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;;;;;;;;;;AAmChB,SAAS,mBAAmB,YAAsD;CAChF,MAAM,YAAiC,EAAE;CAGzC,MAAM,iBAAiB,OAAO,KAAK,WAAW,CAAC;AAC/C,KAAI,iBAAiB,eACnB,OAAM,IAAI,gBACR,mDAAmD,eAAe,YAAY,iBAC/E;AAIH,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,WAAW,EAAE;AAErD,MAAI,IAAI,SAAS,0BACf,OAAM,IAAI,gBACR,0CAA0C,0BAA0B,YAAY,IAAI,SACrF;AAIH,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,2BAC9C,WAAU,OAAO,MAAM,MAAM,GAAG,2BAA2B;MAE3D,WAAU,OAAO;;AAIrB,QAAO;;;;;;AAOT,SAAS,uBAAuB,QAAmF;CACjH,MAAM,WAAgC;EACpC,WAAW,KAAK,KAAK;EACrB,OAAO,OAAO;EACd,KAAK,OAAO;EACb;AAED,KAAI,OAAO,KACT,UAAS,aAAa,mBAAmB,OAAO,KAAK;AAGvD,QAAO,KAAK,UAAU,SAAS;;;;;;;;;;;;;;;;;AAkBjC,IAAa,oBAAb,cAAuC,cAAc;;;;;;CAMnD,YAAY,QAAiC;EAC3C,MAAM,EAAE,QAAQ,UAAU,iBAAiB,GAAG,eAAe;AAE7D,QAAM;GACJ,GAAG;GACH,KAAK,YAAY;GACjB,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,WAAW;IACZ;GACD,iBAAiB,mBAAmB;GAEpC,aAAa,WAAW,eAAe;GACvC,YAAY,WAAW,cAAc;GACrC,YAAY,WAAW,cAAc;GACrC,kBAAkB,WAAW,oBAAoB;GACjD,YAAY,WAAW,cAAc;GACrC,gBAAgB,WAAW,kBAAkB;GAC9C,CAAwB"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loglayer/transport-new-relic",
3
3
  "description": "New Relic transport for the LogLayer logging library.",
4
- "version": "3.0.2",
4
+ "version": "4.0.1",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.mjs",
@@ -34,7 +34,7 @@
34
34
  "transport"
35
35
  ],
36
36
  "dependencies": {
37
- "@loglayer/transport": "3.0.2"
37
+ "@loglayer/transport-http": "2.1.1"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "25.2.3",
@@ -44,12 +44,15 @@
44
44
  "tsx": "4.21.0",
45
45
  "typescript": "5.9.3",
46
46
  "vitest": "4.0.18",
47
- "loglayer": "9.1.0",
48
- "@internal/tsconfig": "2.1.0"
47
+ "@loglayer/transport": "3.0.3",
48
+ "@internal/tsconfig": "2.1.0",
49
+ "loglayer": "9.1.1"
49
50
  },
50
51
  "bugs": "https://github.com/loglayer/loglayer/issues",
51
52
  "engines": {
52
- "node": ">=18"
53
+ "node": ">=18",
54
+ "bun": ">=1.0.0",
55
+ "deno": ">=1.40"
53
56
  },
54
57
  "files": [
55
58
  "dist"