diff --git a/README.md b/README.md index 7a42179..d4b409e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -**@cross/log - Generic logger for Node.js, Deno, and Bun** +**@cross/log - Flexible cross-runtime logging for Node.js, Deno, and Bun** ## Installation @@ -7,14 +7,9 @@ Instructions are available at version: ```bash -# Deno -deno add @cross/log - -# Node.js -npx jsr add @cross/log - -# Bun -bunx jsr add @cross/log +deno add @cross/log # Deno +npx jsr add @cross/log # Node.js +bunx jsr add @cross/log # Bun ``` ## Getting Started @@ -38,7 +33,7 @@ logger.info("Hello log!"); - **Cross-Runtime Compatibility** Works seamlessly across Node.js, Deno, and Bun environments. - **Modular Transports:** Easily create custom transports to send logs to - databases, network services, or specialized systems. + console, file or cloud services like Splunk HEC or New Relic. - **Global and Transport Filtering:** Set log levels globally or configure fine-grained filtering per transport. @@ -102,11 +97,57 @@ try { } ``` +#### New Relic transport + +**Configuration** + +The `NewRelicLogger` transport requires the following options: + +- **`apiKey`:** Your New Relic license key or API key. +- **`region`:** (Optional) The New Relic region where your account is located. + Valid values are "US", "EU", and "FedRamp". Defaults to "US". +- **`serviceAttribute`:** (Optional) A common attribute name to identify the + service generating the logs. +- **`logtypeAttribute`:** (Optional) A common attribute name to categorize your + log entries. +- **`hostnameAttribute`:** (Optional) A common attribute name to indicate the + host where the logs originated. + +**Example** + +```javascript +import { Log, NewRelicLogger } from "@cross/log"; + +const logger = new Log([ + // ... other transports (ConsoleLogger, FileLogger, etc.) + + new NewRelicLogger({ + apiKey: "your-new-relic-api-key", + region: "EU", + serviceAttribute: "my-awesome-app", + logtypeAttribute: "application-logs", + hostnameAttribute: "production-server-1", + }), +]); + +logger.warn("User successfully logged in"); +``` + +**Important Notes** + +- **Obtain your API Key:** You can find your New Relic license key or API key + within your New Relic account settings. +- **Custom Attributes:** The `serviceAttribute`, `logtypeAttribute` and + `hostnameAttribute` help you organize and filter your logs within New Relic. +- **Error Handling:** Consider robust error handling for your New Relic + transport (e.g., storing logs temporarily in a file if the New Relic API is + unavailable). + #### Creating a custom transport ```ts -import { Log, LogTransportBase, LogTransportBaseOptions } from "./mod.ts"; -import { Severity } from "./mod.ts"; +import { Log, LogTransportBase, LogTransportBaseOptions } from "@cross/log"; +import { Severity } from "@cross/log"; /** * Create your own Transport by extending LogTransportBase diff --git a/deno.json b/deno.json index c1c1423..006420d 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@cross/log", - "version": "0.10.1", + "version": "0.10.2", "exports": { ".": "./mod.ts", "./console": "./transports/console.ts", diff --git a/mod.ts b/mod.ts index 56fc09c..3a55e8b 100644 --- a/mod.ts +++ b/mod.ts @@ -9,6 +9,8 @@ export { Severity } from "./src/types.ts"; */ export { ConsoleLogger } from "./transports/console.ts"; export { FileLogger } from "./transports/file.ts"; +export { SplunkHecLogger } from "./transports/splunk.ts"; +export { NewRelicLogger } from "./transports/newrelic.ts"; /** * Re-export of transport classes diff --git a/transports/newrelic.ts b/transports/newrelic.ts new file mode 100644 index 0000000..01c5e6a --- /dev/null +++ b/transports/newrelic.ts @@ -0,0 +1,88 @@ +import { + LogTransport, + LogTransportBase, + LogTransportBaseOptions, +} from "./base.ts"; +import { Severity } from "../src/types.ts"; +import { deepMerge } from "@cross/deepmerge"; + +interface NewRelicLoggerOptions extends LogTransportBaseOptions { + apiKey: string; // The user's New Relic API key + region?: string; // The New Relic region (e.g., 'US', 'EU') + serviceAttribute?: string; + logtypeAttribute?: string; + hostnameAttribute?: string; +} + +export class NewRelicLogger extends LogTransportBase implements LogTransport { + options: NewRelicLoggerOptions; + + constructor(options: NewRelicLoggerOptions) { + super(); + this.options = deepMerge( + this.defaults as NewRelicLoggerOptions, + options, + )!; + } + + log(severity: Severity, scope: string, data: unknown[], timestamp: Date) { + if (this.shouldLog(severity)) { + const event = this.formatEvent(severity, scope, data, timestamp); + this.sendToNewRelic(event); + } + } + + private formatEvent( + severity: Severity, + scope: string, + data: unknown[], + timestamp: Date, + ): object { + return { + logtype: this.options.logtypeAttribute, + service: this.options.serviceAttribute, + hostname: this.options.hostnameAttribute, + timestamp: timestamp.getTime(), + message: data.join(" "), + severity, + scope, + }; + } + + private async sendToNewRelic(event: object) { + const endpoint = this.getEndpoint(); + try { + const response = await fetch(endpoint, { + method: "POST", + body: JSON.stringify(event), + headers: { + "Content-Type": "application/json", + "Api-Key": this.options.apiKey, + }, + }); + if (!response.ok) { + console.error( + "Error sending log event to New Relic: ", + response.status, + response.statusText, + ); + } + } catch (error) { + console.error("Network error sending log event to New Relic:", error); + } + } + + private getEndpoint(): string { + if (this.options.region === "US") { + return "https://log-api.newrelic.com/log/v1"; + } else if (this.options.region === "EU") { + return "https://log-api.eu.newrelic.com/log/v1"; + } else if (this.options.region === "FedRamp") { + return "https://gov-log-api.newrelic.com/log/v1"; + } else { + throw new Error( + "Unknown New Relic Region. Please check your configuration.", + ); + } + } +} diff --git a/transports/splunk.ts b/transports/splunk.ts index c834017..f59ab3c 100644 --- a/transports/splunk.ts +++ b/transports/splunk.ts @@ -2,7 +2,7 @@ import { LogTransport, LogTransportBase, LogTransportBaseOptions, -} from "../src/transport.ts"; +} from "./base.ts"; import { Severity } from "../src/types.ts"; import { deepMerge } from "@cross/deepmerge"; @@ -14,7 +14,7 @@ interface SplunkHecClientOptions extends LogTransportBaseOptions { sourceType?: string; } -export class SplunkHecClient extends LogTransportBase implements LogTransport { +export class SplunkHecLogger extends LogTransportBase implements LogTransport { options: SplunkHecClientOptions; constructor(options: SplunkHecClientOptions) { super();