{"version":3,"sources":["/home/runner/work/loglayer/loglayer/packages/transports/new-relic/dist/index.cjs","../src/NewRelicTransport.ts"],"names":[],"mappings":"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","file":"/home/runner/work/loglayer/loglayer/packages/transports/new-relic/dist/index.cjs","sourcesContent":[null,"import type { LoggerlessTransportConfig, LogLayerTransportParams } from \"@loglayer/transport\";\nimport { LoggerlessTransport } from \"@loglayer/transport\";\n\n// Constants defining New Relic's API limits\nconst MAX_PAYLOAD_SIZE = 1_000_000; // 1MB in bytes\nconst MAX_ATTRIBUTES = 255;\nconst MAX_ATTRIBUTE_NAME_LENGTH = 255;\nconst MAX_ATTRIBUTE_VALUE_LENGTH = 4094;\n\n/**\n * Error thrown when log entry validation fails.\n * This includes payload size, attribute count, and attribute name length validations.\n */\nclass ValidationError extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = \"ValidationError\";\n  }\n}\n\n/**\n * Error thrown when New Relic's API rate limit is exceeded.\n * Contains the retry-after duration specified by the API.\n */\nclass RateLimitError extends Error {\n  constructor(\n    message: string,\n    public retryAfter: number,\n  ) {\n    super(message);\n    this.name = \"RateLimitError\";\n  }\n}\n\n/**\n * Configuration options for the New Relic transport.\n */\nexport interface NewRelicTransportConfig extends LoggerlessTransportConfig {\n  /**\n   * The New Relic API key\n   */\n  apiKey: string;\n  /**\n   * The New Relic Log API endpoint\n   * @default https://log-api.newrelic.com/log/v1\n   */\n  endpoint?: string;\n  /**\n   * Optional callback for error handling\n   */\n  onError?: (err: Error) => void;\n  /**\n   * Optional callback for debugging log entries before they are sent\n   */\n  onDebug?: (entry: Record<string, any>) => void;\n  /**\n   * Whether to use gzip compression\n   * @default true\n   */\n  useCompression?: boolean;\n  /**\n   * Number of retry attempts before giving up\n   * @default 3\n   */\n  maxRetries?: number;\n  /**\n   * Base delay between retries in milliseconds\n   * @default 1000\n   */\n  retryDelay?: number;\n  /**\n   * Whether to respect rate limiting by waiting when a 429 response is received\n   * @default true\n   */\n  respectRateLimit?: boolean;\n}\n\n/**\n * Validates a log entry against New Relic's constraints.\n * - Checks number of attributes (max 255)\n * - Validates attribute name length (max 255 characters)\n * - Truncates attribute values longer than 4094 characters\n *\n * @param logEntry - The log entry to validate\n * @returns The validated (and potentially modified) log entry\n * @throws {ValidationError} If validation fails\n */\nfunction validateLogEntry(logEntry: Record<string, any>) {\n  if (logEntry.attributes) {\n    // Check number of attributes\n    const attributeCount = Object.keys(logEntry.attributes).length;\n    if (attributeCount > MAX_ATTRIBUTES) {\n      throw new ValidationError(\n        `Log entry exceeds maximum number of attributes (${MAX_ATTRIBUTES}). Found: ${attributeCount}`,\n      );\n    }\n\n    // Check attribute names and values\n    for (const [key, value] of Object.entries(logEntry.attributes)) {\n      // Check attribute name length\n      if (key.length > MAX_ATTRIBUTE_NAME_LENGTH) {\n        throw new ValidationError(\n          `Attribute name '${key}' exceeds maximum length (${MAX_ATTRIBUTE_NAME_LENGTH}). Length: ${key.length}`,\n        );\n      }\n\n      // Check string value length\n      if (typeof value === \"string\" && value.length > MAX_ATTRIBUTE_VALUE_LENGTH) {\n        // Truncate the string value to the maximum length\n        logEntry.attributes[key] = value.slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);\n      }\n    }\n  }\n\n  return logEntry;\n}\n\n/**\n * NewRelicTransport is responsible for sending logs to New Relic's Log API.\n * It handles validation, compression, retries, and rate limiting according to New Relic's specifications.\n *\n * Features:\n * - Validates payload size (max 1MB)\n * - Validates number of attributes (max 255)\n * - Validates attribute name length (max 255 characters)\n * - Truncates attribute values longer than 4094 characters\n * - Supports gzip compression\n * - Handles rate limiting with configurable behavior\n * - Implements retry logic with exponential backoff\n */\nexport class NewRelicTransport extends LoggerlessTransport {\n  private apiKey: string;\n  private endpoint: string;\n  private onError?: (err: Error) => void;\n  private onDebug?: (entry: Record<string, any>) => void;\n  private useCompression: boolean;\n  private maxRetries: number;\n  private retryDelay: number;\n  private respectRateLimit: boolean;\n\n  /**\n   * Creates a new instance of NewRelicTransport.\n   *\n   * @param config - Configuration options for the transport\n   * @param config.apiKey - New Relic API key for authentication\n   * @param config.endpoint - Optional custom endpoint URL (defaults to New Relic's Log API endpoint)\n   * @param config.onError - Optional error callback for handling errors\n   * @param config.onDebug - Optional callback for debugging log entries before they are sent\n   * @param config.useCompression - Whether to use gzip compression (defaults to true)\n   * @param config.maxRetries - Maximum number of retry attempts (defaults to 3)\n   * @param config.retryDelay - Base delay between retries in milliseconds (defaults to 1000)\n   * @param config.respectRateLimit - Whether to honor rate limiting headers (defaults to true)\n   */\n  constructor(config: NewRelicTransportConfig) {\n    super(config);\n\n    this.apiKey = config.apiKey;\n    this.endpoint = config.endpoint ?? \"https://log-api.newrelic.com/log/v1\";\n    this.onError = config.onError;\n    this.onDebug = config.onDebug;\n    this.useCompression = config.useCompression ?? true;\n    this.maxRetries = config.maxRetries ?? 3;\n    this.retryDelay = config.retryDelay ?? 1000;\n    this.respectRateLimit = config.respectRateLimit ?? true;\n  }\n\n  /**\n   * Processes and ships log entries to New Relic.\n   *\n   * This method:\n   * 1. Validates the message size\n   * 2. Creates and validates the log entry\n   * 3. Validates the final payload size\n   * 4. Asynchronously sends the log entry to New Relic\n   *\n   * The actual sending is done asynchronously in a fire-and-forget manner to maintain\n   * compatibility with the base transport class while still providing retry and error handling.\n   *\n   * @param params - Log parameters including level, messages, and metadata\n   * @param params.logLevel - The severity level of the log\n   * @param params.messages - Array of message strings to be joined\n   * @param params.data - Optional metadata to include with the log\n   * @param params.hasData - Whether metadata is present\n   * @returns The original messages array\n   * @throws {ValidationError} If the payload exceeds size limits or validation fails\n   */\n  shipToLogger({ logLevel, messages, data, hasData }: LogLayerTransportParams): any[] {\n    try {\n      // Check message size first\n      const message = messages.join(\" \");\n      const messageBytes = new TextEncoder().encode(message).length;\n      if (messageBytes > MAX_PAYLOAD_SIZE) {\n        throw new ValidationError(\n          `Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${messageBytes} bytes`,\n        );\n      }\n\n      const logEntry: Record<string, any> = {\n        timestamp: Date.now(),\n        level: logLevel,\n        log: message,\n      };\n\n      if (data && hasData) {\n        Object.assign(logEntry, {\n          attributes: data,\n        });\n      }\n\n      const validatedEntry = validateLogEntry(logEntry);\n\n      // Call onDebug callback if defined\n      if (this.onDebug) {\n        this.onDebug(validatedEntry);\n      }\n\n      // Check final payload size\n      const payload = JSON.stringify([validatedEntry]);\n      const payloadBytes = new TextEncoder().encode(payload).length;\n      if (payloadBytes > MAX_PAYLOAD_SIZE) {\n        throw new ValidationError(\n          `Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`,\n        );\n      }\n\n      // Fire and forget the async processing\n      (async () => {\n        try {\n          await sendWithRetry(\n            this.endpoint,\n            this.apiKey,\n            payload,\n            this.useCompression,\n            this.maxRetries,\n            this.retryDelay,\n            this.respectRateLimit,\n          );\n        } catch (error) {\n          if (this.onError) {\n            this.onError(error instanceof Error ? error : new Error(String(error)));\n          }\n          // Re-throw validation errors to prevent further processing\n          if (error instanceof ValidationError) {\n            throw error;\n          }\n        }\n      })();\n    } catch (error) {\n      if (this.onError) {\n        this.onError(error instanceof Error ? error : new Error(String(error)));\n      }\n    }\n\n    return messages;\n  }\n}\n\n/**\n * Compresses data using gzip compression.\n *\n * @param data - The string data to compress\n * @returns A promise that resolves to the compressed data as a Uint8Array\n */\nasync function compressData(data: string): Promise<Uint8Array> {\n  const stream = new CompressionStream(\"gzip\");\n  const writer = stream.writable.getWriter();\n  const encoder = new TextEncoder();\n  const chunks: Uint8Array[] = [];\n\n  await writer.write(encoder.encode(data));\n  await writer.close();\n\n  const reader = stream.readable.getReader();\n\n  while (true) {\n    const { value, done } = await reader.read();\n    if (done) break;\n    chunks.push(value);\n  }\n\n  // Combine all chunks into a single Uint8Array\n  const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);\n  const result = new Uint8Array(totalLength);\n  let offset = 0;\n  for (const chunk of chunks) {\n    result.set(chunk, offset);\n    offset += chunk.length;\n  }\n\n  return result;\n}\n\n/**\n * Sends a log entry to New Relic with retry logic.\n * Handles rate limiting, compression, and error cases.\n *\n * @param endpoint - The New Relic API endpoint\n * @param apiKey - The New Relic API key\n * @param payload - The JSON payload to send\n * @param useCompression - Whether to use gzip compression\n * @param maxRetries - Maximum number of retry attempts\n * @param retryDelay - Base delay between retries in milliseconds\n * @param respectRateLimit - Whether to honor rate limiting headers\n * @returns A promise that resolves to the API response\n * @throws {ValidationError} If payload validation fails\n * @throws {RateLimitError} If rate limited and not respecting rate limits\n * @throws {Error} If the request fails after all retries\n */\nasync function sendWithRetry(\n  endpoint: string,\n  apiKey: string,\n  payload: string,\n  useCompression: boolean,\n  maxRetries: number,\n  retryDelay: number,\n  respectRateLimit = true,\n): Promise<Response> {\n  // Check payload size before compression\n  const payloadBytes = new TextEncoder().encode(payload).length;\n  if (payloadBytes > MAX_PAYLOAD_SIZE) {\n    throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`);\n  }\n\n  let lastError: Error;\n  let compressedPayload: Uint8Array | undefined;\n\n  if (useCompression) {\n    compressedPayload = await compressData(payload);\n    // Check compressed payload size\n    if (compressedPayload.length > MAX_PAYLOAD_SIZE) {\n      throw new ValidationError(\n        `Compressed payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${compressedPayload.length} bytes`,\n      );\n    }\n  }\n\n  const headers: Record<string, string> = {\n    \"Content-Type\": \"application/json\",\n    \"Api-Key\": apiKey,\n  };\n\n  if (useCompression) {\n    headers[\"Content-Encoding\"] = \"gzip\";\n  }\n\n  for (let attempt = 0; attempt <= maxRetries; attempt++) {\n    try {\n      const response = await fetch(endpoint, {\n        method: \"POST\",\n        headers,\n        body: useCompression ? compressedPayload : payload,\n      });\n\n      if (response.status === 429) {\n        const retryAfter = Number.parseInt(response.headers.get(\"Retry-After\") || \"60\", 10);\n        if (respectRateLimit) {\n          // Wait for the specified time before retrying\n          await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));\n          // Don't count rate limit retries against maxRetries\n          attempt--;\n          continue;\n        }\n\n        throw new RateLimitError(`Rate limit exceeded. Retry after ${retryAfter} seconds`, retryAfter);\n      }\n\n      if (!response.ok) {\n        throw new Error(`Failed to send logs to New Relic: ${response.statusText}`);\n      }\n\n      return response;\n    } catch (error) {\n      lastError = error instanceof Error ? error : new Error(String(error));\n\n      // Don't retry validation errors\n      if (error instanceof ValidationError) {\n        throw error;\n      }\n\n      // If we're not respecting rate limits, don't retry rate limit errors\n      if (!respectRateLimit && error instanceof RateLimitError) {\n        throw error;\n      }\n\n      if (attempt === maxRetries) {\n        throw new Error(`Failed to send logs after ${maxRetries} retries: ${lastError.message}`);\n      }\n\n      // For non-rate-limit errors, use exponential backoff with jitter\n      if (!(error instanceof RateLimitError)) {\n        const jitter = Math.random() * 200;\n        const delay = retryDelay * 2 ** attempt + jitter;\n        await new Promise((resolve) => setTimeout(resolve, delay));\n      }\n    }\n  }\n\n  throw lastError!;\n}\n"]}