Skip to content

feat: support loglayer logger #184

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions packages/node-opentelemetry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"dependencies": {
"@hyperdx/instrumentation-exception": "^0.1.0",
"@hyperdx/instrumentation-sentry-node": "^0.1.0",
"@loglayer/transport": "^2.2.0",
"@opentelemetry/api": "^1.8.0",
"@opentelemetry/api-logs": "^0.57.2",
"@opentelemetry/auto-instrumentations-node": "^0.56.1",
Expand Down Expand Up @@ -75,6 +76,7 @@
"ioredis": "^5.4.1",
"knex": "^3.1.0",
"koa": "^2.15.3",
"loglayer": "^6.4.2",
"mongodb": "^6.6.2",
"mongoose": "^6.12.8",
"mysql": "^2.18.1",
Expand Down
27 changes: 27 additions & 0 deletions packages/node-opentelemetry/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type { LogLevelType } from '@loglayer/transport';
import opentelemetry, { diag } from '@opentelemetry/api';

import {
DEFAULT_HDX_API_KEY,
DEFAULT_HDX_NODE_BETA_MODE,
DEFAULT_SERVICE_NAME,
} from './constants';
import type { HyperDXLogLayerOptions } from './otel-logger/loglayer';
import HyperDXLogLayer from './otel-logger/loglayer';
import type { HyperDXPinoOptions } from './otel-logger/pino';
import * as HyperDXPino from './otel-logger/pino';
import type { HyperDXWinstonOptions } from './otel-logger/winston';
Expand All @@ -20,6 +23,11 @@ type PinotTransportOptions = Omit<
'apiKey' | 'getCustomMeta' | 'resourceAttributes'
>;

type LogLayerTransportOptions = Omit<
HyperDXLogLayerOptions,
'apiKey' | 'getCustomMeta' | 'resourceAttributes'
>;

const getCustomMeta = () => {
//@ts-ignore
const contextManager = opentelemetry.context?._getContextManager();
Expand Down Expand Up @@ -79,3 +87,22 @@ export const getPinoTransport = (
level: maxLevel,
};
};

export const getLogLayerTransport = (
maxLevel: LogLevelType = 'info',
options: LogLayerTransportOptions = {},
) => {
diag.debug('Initializing LogLayer transport');
const apiKey = DEFAULT_HDX_API_KEY();
return new HyperDXLogLayer({
...(apiKey && {
headers: {
Authorization: apiKey,
},
}),
service: DEFAULT_SERVICE_NAME(),
getCustomMeta: DEFAULT_HDX_NODE_BETA_MODE() ? getCustomMeta : () => ({}),
maxLevel,
...options,
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { LogLayer, LogLevel } from 'loglayer';

import { getLogLayerTransport } from '../../logger';

describe('LogLayer transport', () => {
it('should initialize and send logs', () => {
const logger = new LogLayer({
transport: getLogLayerTransport(),
});

// Test basic logging
logger.info('test message');
logger.error('error message');
logger.warn('warning message');
logger.debug('debug message');
});

it('should handle metadata', () => {
const logger = new LogLayer({
transport: getLogLayerTransport(),
});

// Test with metadata
logger.withMetadata({ service: 'test' }).info('message with metadata');

// Test with multiple metadata calls
logger
.withMetadata({ service: 'test' })
.withMetadata({ requestId: '123' })
.info('message with multiple metadata');
});

it('should handle transport cleanup', () => {
const logger = new LogLayer({
transport: getLogLayerTransport(),
});

// Send some logs
logger.info('test message');

// Clean up the transport
logger.withFreshTransports([]);

// This should not throw
logger.info('message after cleanup');
});

it('should respect maxLevel', () => {
const logger = new LogLayer({
transport: getLogLayerTransport(LogLevel.warn),
});

// These should be filtered out
logger.trace('trace message');
logger.debug('debug message');
logger.info('info message');

// These should be sent
logger.warn('warning message');
logger.error('error message');
logger.fatal('fatal message');
});
});
100 changes: 100 additions & 0 deletions packages/node-opentelemetry/src/otel-logger/loglayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
LoggerlessTransport,
type LogLayerTransportParams,
LogLevel,
type LogLevelType,
} from '@loglayer/transport';
import { Attributes, diag } from '@opentelemetry/api';

import type { LoggerOptions } from './';
import { Logger } from './';

// Map LogLayer levels to their numeric values for comparison
const LOG_LEVEL_PRIORITY = {
[LogLevel.trace]: 0,
[LogLevel.debug]: 1,
[LogLevel.info]: 2,
[LogLevel.warn]: 3,
[LogLevel.error]: 4,
[LogLevel.fatal]: 5,
};

export type HyperDXLogLayerOptions = LoggerOptions & {
apiKey?: string;
getCustomMeta?: () => Attributes;
maxLevel?: LogLevelType;
};

export default class HyperDXLogLayer
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For more info on how this transport is implemented:

https://loglayer.dev/transports/creating-transports.html

extends LoggerlessTransport
implements Disposable
{
private readonly logger: Logger;
private readonly getCustomMeta: (() => Attributes) | undefined;
private readonly maxLevel: LogLevelType;
private isDisposed = false;

constructor({
getCustomMeta,
apiKey,
maxLevel = 'info',
...options
}: HyperDXLogLayerOptions) {
diag.debug('Initializing HyperDX LogLayer transport...');
super({ level: maxLevel });
this.getCustomMeta = getCustomMeta;
this.maxLevel = maxLevel;
this.logger = new Logger({
...(apiKey && {
headers: {
Authorization: apiKey,
},
}),
...options,
});
diag.debug('HyperDX LogLayer transport initialized!');
}

shipToLogger({
logLevel,
messages,
data,
hasData,
}: LogLayerTransportParams): string[] {
if (this.isDisposed) return messages;

// Skip logs below maxLevel
if (LOG_LEVEL_PRIORITY[logLevel] < LOG_LEVEL_PRIORITY[this.maxLevel]) {
return messages;
}

diag.debug('Received log from LogLayer');
const message = messages.join(' ');
const meta = {
...(data && hasData ? data : {}),
...this.getCustomMeta?.(),
};

diag.debug('Sending log to HyperDX');
this.logger.postMessage(logLevel, message, meta);
diag.debug('Log sent to HyperDX');

return messages;
}

[Symbol.dispose](): void {
if (this.isDisposed) return;

diag.debug('Closing HyperDX LogLayer transport...');
this.logger
.shutdown()
.then(() => {
diag.debug('HyperDX LogLayer transport closed!');
this.isDisposed = true;
})
.catch((err) => {
console.error('Error closing HyperDX LogLayer transport:', err);
this.isDisposed = true;
});
}
}
82 changes: 76 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2698,6 +2698,32 @@
methods "^1.1.2"
path-to-regexp "^6.2.1"

"@loglayer/[email protected]":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@loglayer/context-manager/-/context-manager-1.1.0.tgz#3dab8b29b55838e2941a69aa20ec4dfbf39bb963"
integrity sha512-kTJ7TgItEvHuQWgGybsCs4TLyUxR4+Jdqq+sj6EWNd8tJVFGZJ/NolfkixZUNNWDcB72+P6oU8TcsnSSLoJT/Q==
dependencies:
"@loglayer/shared" "2.3.0"

"@loglayer/[email protected]":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@loglayer/plugin/-/plugin-2.1.0.tgz#76dd34f9addd5f8706c89674bcf993e1789f6b8a"
integrity sha512-MGAjkWPBjChf8B9kDfNy8qkJ/RK0KIaK6m77JkR6aqr2xF4mTV4HhTYom9nM5iq6TJW36PXNWRV5CWDh9HTZQA==
dependencies:
"@loglayer/shared" "2.3.0"

"@loglayer/[email protected]":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@loglayer/shared/-/shared-2.3.0.tgz#2a4ed585a44ef446fe621ddd309a2508cf4c2d59"
integrity sha512-jBg8RC13ytx7ZfWOAdlYOt61T2GrbsJpNDrAmuK/flOhNW30OENn21l7Ns28L986QfTRPJyVtKxe5lqFN2w3vg==

"@loglayer/[email protected]", "@loglayer/transport@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@loglayer/transport/-/transport-2.2.0.tgz#a9c880ae02799749325bdb318a3155ca294a5e6e"
integrity sha512-X6j9o9Q+xuGL3HnNu+dcAlBgf+9LBswsVK9MOKQJdOSCwQZpSrEBilaZdecuLhoczZsqIu11cbRXzAsbFj06sA==
dependencies:
"@loglayer/shared" "2.3.0"

"@lukeed/csprng@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe"
Expand Down Expand Up @@ -4078,13 +4104,12 @@
lru_map "^0.3.3"
tslib "^1.9.3"

"@sentry/types-v7@npm:@sentry/[email protected]", "@sentry/[email protected]":
name "@sentry/types-v7"
"@sentry/types-v7@npm:@sentry/[email protected]":
version "7.116.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.116.0.tgz#0be3434e7e53c86db4993e668af1c3a65bfb7519"
integrity sha512-QCCvG5QuQrwgKzV11lolNQPP2k67Q6HHD9vllZ/C4dkxkjoIym8Gy+1OgAN3wjsR0f/kG9o5iZyglgNpUVRapQ==

"@sentry/types-v8@npm:@sentry/[email protected]", "@sentry/[email protected]", "@sentry/types@^8.7.0":
"@sentry/types-v8@npm:@sentry/[email protected]":
version "8.7.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.7.0.tgz#92731af32318d6abb8759216cf6c3c5035894e6e"
integrity sha512-11KLOKumP6akugVGLvSoEig+JlP0ZEzW3nN9P+ppgdIx9HAxMIh6UvumbieG4/DWjAh2kh6NPNfUw3gk2Gfq1A==
Expand All @@ -4094,6 +4119,16 @@
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.19.7.tgz#c6b337912e588083fc2896eb012526cf7cfec7c7"
integrity sha512-jH84pDYE+hHIbVnab3Hr+ZXr1v8QABfhx39KknxqKWr2l0oEItzepV0URvbEhB446lk/S/59230dlUUIBGsXbg==

"@sentry/[email protected]":
version "7.116.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.116.0.tgz#0be3434e7e53c86db4993e668af1c3a65bfb7519"
integrity sha512-QCCvG5QuQrwgKzV11lolNQPP2k67Q6HHD9vllZ/C4dkxkjoIym8Gy+1OgAN3wjsR0f/kG9o5iZyglgNpUVRapQ==

"@sentry/[email protected]", "@sentry/types@^8.7.0":
version "8.7.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.7.0.tgz#92731af32318d6abb8759216cf6c3c5035894e6e"
integrity sha512-11KLOKumP6akugVGLvSoEig+JlP0ZEzW3nN9P+ppgdIx9HAxMIh6UvumbieG4/DWjAh2kh6NPNfUw3gk2Gfq1A==

"@sentry/[email protected]":
version "6.19.7"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.19.7.tgz#6edd739f8185fd71afe49cbe351c1bbf5e7b7c79"
Expand Down Expand Up @@ -10307,6 +10342,16 @@ logform@^2.3.2, logform@^2.4.0:
safe-stable-stringify "^2.3.1"
triple-beam "^1.3.0"

loglayer@^6.4.2:
version "6.4.2"
resolved "https://registry.yarnpkg.com/loglayer/-/loglayer-6.4.2.tgz#fc74389f70fdd3e074b225c48d2583ffb642c179"
integrity sha512-t5L6Nh7uH+f7GGli9UJa2krvzblkiVzllf6+/h5xa26Gr60yDj3lMGH3ZoLyFZjMBbNtpsDs3UVeA5dVl8M2CQ==
dependencies:
"@loglayer/context-manager" "1.1.0"
"@loglayer/plugin" "2.1.0"
"@loglayer/shared" "2.3.0"
"@loglayer/transport" "2.2.0"

long@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
Expand Down Expand Up @@ -13272,7 +13317,16 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"

"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"

string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
Expand Down Expand Up @@ -13342,7 +13396,14 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"

"strip-ansi-cjs@npm:strip-ansi@^6.0.1", [email protected], strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

[email protected], strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
Expand Down Expand Up @@ -14411,7 +14472,7 @@ [email protected]:
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==

"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
Expand All @@ -14429,6 +14490,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
Expand Down