Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silent-timers-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@logdash/node": patch
---

feat: minor changes
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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

Expand Down
Binary file removed docs/delta.png
Binary file not shown.
Binary file added docs/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/logs.png
Binary file not shown.
74 changes: 45 additions & 29 deletions src/Logdash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, [number, number, number]> = {
[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<LogPayload>;
metricQueue: RequestQueue<MetricPayload>;
logQueue: RequestQueue<LogPayload> | null;
metricQueue: RequestQueue<MetricPayload> | null;
sequenceNumber: number;
verbose: boolean;
}
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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}`);
}
Expand All @@ -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}`);
}
Expand All @@ -141,15 +153,19 @@ export class Logdash {
// === Lifecycle ===

async flush(): Promise<void> {
if (!this.core.logQueue || !this.core.metricQueue) {
return; // Local mode: nothing to flush
}

await Promise.all([
this.core.logQueue.flush(),
this.core.metricQueue.flush(),
]);
}

destroy(): void {
this.core.logQueue.destroy();
this.core.metricQueue.destroy();
this.core.logQueue?.destroy();
this.core.metricQueue?.destroy();
}

// === Private Methods ===
Expand All @@ -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(),
Expand Down
39 changes: 26 additions & 13 deletions src/logger/internalLogger.ts
Original file line number Diff line number Diff line change
@@ -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),
};
10 changes: 10 additions & 0 deletions src/types/LogLevel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,13 @@ export enum LogLevel {
DEBUG = 'debug',
SILLY = 'silly',
}

export const LOG_LEVEL_COLORS: Record<LogLevel, [number, number, number]> = {
[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],
};
13 changes: 11 additions & 2 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
});