diff --git a/.changeset/silent-timers-exercise.md b/.changeset/silent-timers-exercise.md new file mode 100644 index 0000000..ee17bb0 --- /dev/null +++ b/.changeset/silent-timers-exercise.md @@ -0,0 +1,5 @@ +--- +"@logdash/node": patch +--- + +feat: minor changes diff --git a/README.md b/README.md index 94f0e79..e56c41b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # @logdash/node -Logdash is a zero-config observability platform. This package serves a Node.js/Bun/Deno/Browser interface to use it. +Logdash is a zero-config observability platform. This package serves a NodeJS/Bun/Deno interface to use it. ## Pre-requisites @@ -57,8 +57,7 @@ await logdash.flush(); To see the logs or metrics, go to your project dashboard -![logs](docs/logs.png) -![delta](docs/delta.png) +![dashboard](docs/image.png) ## Configuration diff --git a/docs/delta.png b/docs/delta.png deleted file mode 100644 index b311d3d..0000000 Binary files a/docs/delta.png and /dev/null differ diff --git a/docs/image.png b/docs/image.png new file mode 100644 index 0000000..f7f8527 Binary files /dev/null and b/docs/image.png differ diff --git a/docs/logs.png b/docs/logs.png deleted file mode 100644 index 62f6872..0000000 Binary files a/docs/logs.png and /dev/null differ diff --git a/src/Logdash.ts b/src/Logdash.ts index 7f31cf2..bd5ae4d 100644 --- a/src/Logdash.ts +++ b/src/Logdash.ts @@ -6,26 +6,16 @@ import { LogPayload, MetricPayload, } from './transport/HttpTransport.js'; -import { LogLevel } from './types/LogLevel.js'; +import { LogLevel, LOG_LEVEL_COLORS } from './types/LogLevel.js'; export interface LogdashOptions { host?: string; verbose?: boolean; } -const LOG_LEVEL_COLORS: Record = { - [LogLevel.ERROR]: [231, 0, 11], - [LogLevel.WARN]: [254, 154, 0], - [LogLevel.INFO]: [21, 93, 252], - [LogLevel.HTTP]: [0, 166, 166], - [LogLevel.VERBOSE]: [0, 166, 0], - [LogLevel.DEBUG]: [0, 166, 62], - [LogLevel.SILLY]: [80, 80, 80], -}; - interface LogdashCore { - logQueue: RequestQueue; - metricQueue: RequestQueue; + logQueue: RequestQueue | null; + metricQueue: RequestQueue | null; sequenceNumber: number; verbose: boolean; } @@ -46,21 +36,35 @@ export class Logdash { this.namespace = optionsOrNamespace as string; } else { // Public constructor: new Logdash(apiKey?, options?) - const apiKey = apiKeyOrCore ?? ''; + const apiKey = apiKeyOrCore; const options = optionsOrNamespace as LogdashOptions | undefined; - const host = options?.host ?? 'https://api.logdash.io'; const verbose = options?.verbose ?? false; - const transport = new HttpTransport({ host, apiKey }); - - this.core = { - logQueue: new RequestQueue((logs) => transport.sendLogs(logs)), - metricQueue: new RequestQueue((metrics) => - transport.sendMetrics(metrics), - ), - sequenceNumber: 0, - verbose, - }; + if (apiKey) { + // Remote mode: create transport and queues + const host = options?.host ?? 'https://api.logdash.io'; + const transport = new HttpTransport({ host, apiKey }); + + this.core = { + logQueue: new RequestQueue((logs) => + transport.sendLogs(logs), + ), + metricQueue: new RequestQueue((metrics) => + transport.sendMetrics(metrics), + ), + sequenceNumber: 0, + verbose, + }; + } else { + internalLogger.warn('No API key provided, using local mode.'); + // Local mode: console-only, no transport or queues + this.core = { + logQueue: null, + metricQueue: null, + sequenceNumber: 0, + verbose, + }; + } this.namespace = undefined; } } @@ -107,6 +111,10 @@ export class Logdash { // === Metric Methods === setMetric(name: string, value: number): void { + if (!this.core.metricQueue) { + return; // Local mode: metrics are not supported + } + if (this.core.verbose) { internalLogger.verbose(`Setting metric ${name} to ${value}`); } @@ -120,6 +128,10 @@ export class Logdash { } mutateMetric(name: string, delta: number): void { + if (!this.core.metricQueue) { + return; // Local mode: metrics are not supported + } + if (this.core.verbose) { internalLogger.verbose(`Mutating metric ${name} by ${delta}`); } @@ -141,6 +153,10 @@ export class Logdash { // === Lifecycle === async flush(): Promise { + if (!this.core.logQueue || !this.core.metricQueue) { + return; // Local mode: nothing to flush + } + await Promise.all([ this.core.logQueue.flush(), this.core.metricQueue.flush(), @@ -148,8 +164,8 @@ export class Logdash { } destroy(): void { - this.core.logQueue.destroy(); - this.core.metricQueue.destroy(); + this.core.logQueue?.destroy(); + this.core.metricQueue?.destroy(); } // === Private Methods === @@ -161,8 +177,8 @@ export class Logdash { // Print to console with colors this.printToConsole(level, message, now); - // Queue for sending - this.core.logQueue.add({ + // Queue for sending (only in remote mode) + this.core.logQueue?.add({ message, level, createdAt: now.toISOString(), diff --git a/src/logger/internalLogger.ts b/src/logger/internalLogger.ts index 1b8b2c0..c100f6f 100644 --- a/src/logger/internalLogger.ts +++ b/src/logger/internalLogger.ts @@ -1,18 +1,31 @@ import chalk from 'chalk'; +import { LogLevel, LOG_LEVEL_COLORS } from '../types/LogLevel.js'; -const PREFIX = chalk.rgb(230, 0, 118)('[Logdash]'); +const NAMESPACE = 'Logdash'; + +function formatMessage(level: LogLevel, args: unknown[]): void { + const color = LOG_LEVEL_COLORS[level]; + + const levelPrefix = chalk.rgb( + color[0], + color[1], + color[2], + )(`${level.toUpperCase()} `); + const namespacePrefix = chalk.rgb(230, 0, 118)(`${NAMESPACE} `); + const message = args + .map((item) => + typeof item === 'object' && item !== null + ? JSON.stringify(item) + : String(item), + ) + .join(' '); + + console.log(`${namespacePrefix}${levelPrefix}${message}\n`); +} export const internalLogger = { - error: (...args: unknown[]) => { - console.log(PREFIX, chalk.red('ERROR'), ...args); - }, - warn: (...args: unknown[]) => { - console.log(PREFIX, chalk.yellow('WARN'), ...args); - }, - info: (...args: unknown[]) => { - console.log(PREFIX, ...args); - }, - verbose: (...args: unknown[]) => { - console.log(PREFIX, chalk.gray(...args)); - }, + error: (...args: unknown[]) => formatMessage(LogLevel.ERROR, args), + warn: (...args: unknown[]) => formatMessage(LogLevel.WARN, args), + info: (...args: unknown[]) => formatMessage(LogLevel.INFO, args), + verbose: (...args: unknown[]) => formatMessage(LogLevel.VERBOSE, args), }; diff --git a/src/types/LogLevel.ts b/src/types/LogLevel.ts index d1545bd..404c4f9 100644 --- a/src/types/LogLevel.ts +++ b/src/types/LogLevel.ts @@ -7,3 +7,13 @@ export enum LogLevel { DEBUG = 'debug', SILLY = 'silly', } + +export const LOG_LEVEL_COLORS: Record = { + [LogLevel.ERROR]: [231, 0, 11], + [LogLevel.WARN]: [254, 154, 0], + [LogLevel.INFO]: [21, 93, 252], + [LogLevel.HTTP]: [0, 166, 166], + [LogLevel.VERBOSE]: [0, 166, 0], + [LogLevel.DEBUG]: [0, 166, 62], + [LogLevel.SILLY]: [80, 80, 80], +}; diff --git a/test.ts b/test.ts index aa82b64..3eea7c1 100644 --- a/test.ts +++ b/test.ts @@ -13,14 +13,22 @@ logdash.debug('This is a debug message'); logdash.silly('This is a silly message'); // Usage with API key -const syncedLogdash = new Logdash('MY_API_KEY'); +const syncedLogdash = new Logdash('API_KEY', { + host: 'https://dev-api.logdash.io', +}); syncedLogdash.error('This is a SYNCED error message'); // Namespaced logging const authLogger = syncedLogdash.withNamespace('auth'); authLogger.info('User logged in'); -authLogger.setMetric('login_count', 1); +authLogger.mutateMetric('login_count', 1); + +const paymentsLogger = syncedLogdash.withNamespace('payments'); +paymentsLogger.info('Payment processed'); +paymentsLogger.warn('Payment gate not responding in 5s'); +paymentsLogger.error('Payment failed'); +paymentsLogger.mutateMetric('payment_count', 1); // Metrics syncedLogdash.setMetric('active_users', 42); @@ -29,4 +37,5 @@ syncedLogdash.mutateMetric('requests', 1); // Graceful shutdown - wait for all pending items syncedLogdash.flush().then(() => { console.log('All logs and metrics flushed!'); + process.exit(0); });