Skip to content

Commit 05a16d6

Browse files
committed
feat: support loglayer logger
1 parent 7831007 commit 05a16d6

File tree

5 files changed

+268
-6
lines changed

5 files changed

+268
-6
lines changed

packages/node-opentelemetry/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"dependencies": {
3535
"@hyperdx/instrumentation-exception": "^0.1.0",
3636
"@hyperdx/instrumentation-sentry-node": "^0.1.0",
37+
"@loglayer/transport": "^2.2.0",
3738
"@opentelemetry/api": "^1.8.0",
3839
"@opentelemetry/api-logs": "^0.57.2",
3940
"@opentelemetry/auto-instrumentations-node": "^0.56.1",
@@ -75,6 +76,7 @@
7576
"ioredis": "^5.4.1",
7677
"knex": "^3.1.0",
7778
"koa": "^2.15.3",
79+
"loglayer": "^6.4.2",
7880
"mongodb": "^6.6.2",
7981
"mongoose": "^6.12.8",
8082
"mysql": "^2.18.1",

packages/node-opentelemetry/src/logger.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import type { LogLevelType } from '@loglayer/transport';
12
import opentelemetry, { diag } from '@opentelemetry/api';
23

34
import {
45
DEFAULT_HDX_API_KEY,
56
DEFAULT_HDX_NODE_BETA_MODE,
67
DEFAULT_SERVICE_NAME,
78
} from './constants';
9+
import type { HyperDXLogLayerOptions } from './otel-logger/loglayer';
10+
import HyperDXLogLayer from './otel-logger/loglayer';
811
import type { HyperDXPinoOptions } from './otel-logger/pino';
912
import * as HyperDXPino from './otel-logger/pino';
1013
import type { HyperDXWinstonOptions } from './otel-logger/winston';
@@ -20,6 +23,11 @@ type PinotTransportOptions = Omit<
2023
'apiKey' | 'getCustomMeta' | 'resourceAttributes'
2124
>;
2225

26+
type LogLayerTransportOptions = Omit<
27+
HyperDXLogLayerOptions,
28+
'apiKey' | 'getCustomMeta' | 'resourceAttributes'
29+
>;
30+
2331
const getCustomMeta = () => {
2432
//@ts-ignore
2533
const contextManager = opentelemetry.context?._getContextManager();
@@ -79,3 +87,22 @@ export const getPinoTransport = (
7987
level: maxLevel,
8088
};
8189
};
90+
91+
export const getLogLayerTransport = (
92+
maxLevel: LogLevelType = 'info',
93+
options: LogLayerTransportOptions = {},
94+
) => {
95+
diag.debug('Initializing LogLayer transport');
96+
const apiKey = DEFAULT_HDX_API_KEY();
97+
return new HyperDXLogLayer({
98+
...(apiKey && {
99+
headers: {
100+
Authorization: apiKey,
101+
},
102+
}),
103+
service: DEFAULT_SERVICE_NAME(),
104+
getCustomMeta: DEFAULT_HDX_NODE_BETA_MODE() ? getCustomMeta : () => ({}),
105+
maxLevel,
106+
...options,
107+
});
108+
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { LogLayer, LogLevel } from 'loglayer';
2+
3+
import { getLogLayerTransport } from '../../logger';
4+
5+
describe('LogLayer transport', () => {
6+
it('should initialize and send logs', () => {
7+
const logger = new LogLayer({
8+
transport: getLogLayerTransport(),
9+
});
10+
11+
// Test basic logging
12+
logger.info('test message');
13+
logger.error('error message');
14+
logger.warn('warning message');
15+
logger.debug('debug message');
16+
});
17+
18+
it('should handle metadata', () => {
19+
const logger = new LogLayer({
20+
transport: getLogLayerTransport(),
21+
});
22+
23+
// Test with metadata
24+
logger.withMetadata({ service: 'test' }).info('message with metadata');
25+
26+
// Test with multiple metadata calls
27+
logger
28+
.withMetadata({ service: 'test' })
29+
.withMetadata({ requestId: '123' })
30+
.info('message with multiple metadata');
31+
});
32+
33+
it('should handle transport cleanup', () => {
34+
const logger = new LogLayer({
35+
transport: getLogLayerTransport(),
36+
});
37+
38+
// Send some logs
39+
logger.info('test message');
40+
41+
// Clean up the transport
42+
logger.withFreshTransports([]);
43+
44+
// This should not throw
45+
logger.info('message after cleanup');
46+
});
47+
48+
it('should respect maxLevel', () => {
49+
const logger = new LogLayer({
50+
transport: getLogLayerTransport(LogLevel.warn),
51+
});
52+
53+
// These should be filtered out
54+
logger.trace('trace message');
55+
logger.debug('debug message');
56+
logger.info('info message');
57+
58+
// These should be sent
59+
logger.warn('warning message');
60+
logger.error('error message');
61+
logger.fatal('fatal message');
62+
});
63+
});
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import {
2+
LoggerlessTransport,
3+
type LogLayerTransportParams,
4+
LogLevel,
5+
type LogLevelType,
6+
} from '@loglayer/transport';
7+
import { Attributes, diag } from '@opentelemetry/api';
8+
9+
import type { LoggerOptions } from './';
10+
import { Logger } from './';
11+
12+
// Map LogLayer levels to their numeric values for comparison
13+
const LOG_LEVEL_PRIORITY = {
14+
[LogLevel.trace]: 0,
15+
[LogLevel.debug]: 1,
16+
[LogLevel.info]: 2,
17+
[LogLevel.warn]: 3,
18+
[LogLevel.error]: 4,
19+
[LogLevel.fatal]: 5,
20+
};
21+
22+
export type HyperDXLogLayerOptions = LoggerOptions & {
23+
apiKey?: string;
24+
getCustomMeta?: () => Attributes;
25+
maxLevel?: LogLevelType;
26+
};
27+
28+
export default class HyperDXLogLayer
29+
extends LoggerlessTransport
30+
implements Disposable
31+
{
32+
private readonly logger: Logger;
33+
private readonly getCustomMeta: (() => Attributes) | undefined;
34+
private readonly maxLevel: LogLevelType;
35+
private isDisposed = false;
36+
37+
constructor({
38+
getCustomMeta,
39+
apiKey,
40+
maxLevel = 'info',
41+
...options
42+
}: HyperDXLogLayerOptions) {
43+
diag.debug('Initializing HyperDX LogLayer transport...');
44+
super({ level: maxLevel });
45+
this.getCustomMeta = getCustomMeta;
46+
this.maxLevel = maxLevel;
47+
this.logger = new Logger({
48+
...(apiKey && {
49+
headers: {
50+
Authorization: apiKey,
51+
},
52+
}),
53+
...options,
54+
});
55+
diag.debug('HyperDX LogLayer transport initialized!');
56+
}
57+
58+
shipToLogger({
59+
logLevel,
60+
messages,
61+
data,
62+
hasData,
63+
}: LogLayerTransportParams): string[] {
64+
if (this.isDisposed) return messages;
65+
66+
// Skip logs below maxLevel
67+
if (LOG_LEVEL_PRIORITY[logLevel] < LOG_LEVEL_PRIORITY[this.maxLevel]) {
68+
return messages;
69+
}
70+
71+
diag.debug('Received log from LogLayer');
72+
const message = messages.join(' ');
73+
const meta = {
74+
...(data && hasData ? data : {}),
75+
...this.getCustomMeta?.(),
76+
};
77+
78+
diag.debug('Sending log to HyperDX');
79+
this.logger.postMessage(logLevel, message, meta);
80+
diag.debug('Log sent to HyperDX');
81+
82+
return messages;
83+
}
84+
85+
[Symbol.dispose](): void {
86+
if (this.isDisposed) return;
87+
88+
diag.debug('Closing HyperDX LogLayer transport...');
89+
this.logger
90+
.shutdown()
91+
.then(() => {
92+
diag.debug('HyperDX LogLayer transport closed!');
93+
this.isDisposed = true;
94+
})
95+
.catch((err) => {
96+
console.error('Error closing HyperDX LogLayer transport:', err);
97+
this.isDisposed = true;
98+
});
99+
}
100+
}

yarn.lock

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2698,6 +2698,32 @@
26982698
methods "^1.1.2"
26992699
path-to-regexp "^6.2.1"
27002700

2701+
"@loglayer/[email protected]":
2702+
version "1.1.0"
2703+
resolved "https://registry.yarnpkg.com/@loglayer/context-manager/-/context-manager-1.1.0.tgz#3dab8b29b55838e2941a69aa20ec4dfbf39bb963"
2704+
integrity sha512-kTJ7TgItEvHuQWgGybsCs4TLyUxR4+Jdqq+sj6EWNd8tJVFGZJ/NolfkixZUNNWDcB72+P6oU8TcsnSSLoJT/Q==
2705+
dependencies:
2706+
"@loglayer/shared" "2.3.0"
2707+
2708+
"@loglayer/[email protected]":
2709+
version "2.1.0"
2710+
resolved "https://registry.yarnpkg.com/@loglayer/plugin/-/plugin-2.1.0.tgz#76dd34f9addd5f8706c89674bcf993e1789f6b8a"
2711+
integrity sha512-MGAjkWPBjChf8B9kDfNy8qkJ/RK0KIaK6m77JkR6aqr2xF4mTV4HhTYom9nM5iq6TJW36PXNWRV5CWDh9HTZQA==
2712+
dependencies:
2713+
"@loglayer/shared" "2.3.0"
2714+
2715+
"@loglayer/[email protected]":
2716+
version "2.3.0"
2717+
resolved "https://registry.yarnpkg.com/@loglayer/shared/-/shared-2.3.0.tgz#2a4ed585a44ef446fe621ddd309a2508cf4c2d59"
2718+
integrity sha512-jBg8RC13ytx7ZfWOAdlYOt61T2GrbsJpNDrAmuK/flOhNW30OENn21l7Ns28L986QfTRPJyVtKxe5lqFN2w3vg==
2719+
2720+
"@loglayer/[email protected]", "@loglayer/transport@^2.2.0":
2721+
version "2.2.0"
2722+
resolved "https://registry.yarnpkg.com/@loglayer/transport/-/transport-2.2.0.tgz#a9c880ae02799749325bdb318a3155ca294a5e6e"
2723+
integrity sha512-X6j9o9Q+xuGL3HnNu+dcAlBgf+9LBswsVK9MOKQJdOSCwQZpSrEBilaZdecuLhoczZsqIu11cbRXzAsbFj06sA==
2724+
dependencies:
2725+
"@loglayer/shared" "2.3.0"
2726+
27012727
"@lukeed/csprng@^1.0.0":
27022728
version "1.1.0"
27032729
resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe"
@@ -4078,13 +4104,12 @@
40784104
lru_map "^0.3.3"
40794105
tslib "^1.9.3"
40804106

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

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

4122+
4123+
version "7.116.0"
4124+
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.116.0.tgz#0be3434e7e53c86db4993e668af1c3a65bfb7519"
4125+
integrity sha512-QCCvG5QuQrwgKzV11lolNQPP2k67Q6HHD9vllZ/C4dkxkjoIym8Gy+1OgAN3wjsR0f/kG9o5iZyglgNpUVRapQ==
4126+
4127+
"@sentry/[email protected]", "@sentry/types@^8.7.0":
4128+
version "8.7.0"
4129+
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.7.0.tgz#92731af32318d6abb8759216cf6c3c5035894e6e"
4130+
integrity sha512-11KLOKumP6akugVGLvSoEig+JlP0ZEzW3nN9P+ppgdIx9HAxMIh6UvumbieG4/DWjAh2kh6NPNfUw3gk2Gfq1A==
4131+
40974132
40984133
version "6.19.7"
40994134
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.19.7.tgz#6edd739f8185fd71afe49cbe351c1bbf5e7b7c79"
@@ -10307,6 +10342,16 @@ logform@^2.3.2, logform@^2.4.0:
1030710342
safe-stable-stringify "^2.3.1"
1030810343
triple-beam "^1.3.0"
1030910344

10345+
loglayer@^6.4.2:
10346+
version "6.4.2"
10347+
resolved "https://registry.yarnpkg.com/loglayer/-/loglayer-6.4.2.tgz#fc74389f70fdd3e074b225c48d2583ffb642c179"
10348+
integrity sha512-t5L6Nh7uH+f7GGli9UJa2krvzblkiVzllf6+/h5xa26Gr60yDj3lMGH3ZoLyFZjMBbNtpsDs3UVeA5dVl8M2CQ==
10349+
dependencies:
10350+
"@loglayer/context-manager" "1.1.0"
10351+
"@loglayer/plugin" "2.1.0"
10352+
"@loglayer/shared" "2.3.0"
10353+
"@loglayer/transport" "2.2.0"
10354+
1031010355
long@^4.0.0:
1031110356
version "4.0.0"
1031210357
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
@@ -13272,7 +13317,16 @@ string-length@^4.0.1:
1327213317
char-regex "^1.0.2"
1327313318
strip-ansi "^6.0.0"
1327413319

13275-
"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:
13320+
"string-width-cjs@npm:string-width@^4.2.0":
13321+
version "4.2.3"
13322+
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
13323+
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
13324+
dependencies:
13325+
emoji-regex "^8.0.0"
13326+
is-fullwidth-code-point "^3.0.0"
13327+
strip-ansi "^6.0.1"
13328+
13329+
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:
1327613330
version "4.2.3"
1327713331
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
1327813332
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -13342,7 +13396,14 @@ string_decoder@~1.1.1:
1334213396
dependencies:
1334313397
safe-buffer "~5.1.0"
1334413398

13345-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", [email protected], strip-ansi@^6.0.0, strip-ansi@^6.0.1:
13399+
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
13400+
version "6.0.1"
13401+
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
13402+
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
13403+
dependencies:
13404+
ansi-regex "^5.0.1"
13405+
13406+
[email protected], strip-ansi@^6.0.0, strip-ansi@^6.0.1:
1334613407
version "6.0.1"
1334713408
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
1334813409
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -14411,7 +14472,7 @@ [email protected]:
1441114472
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
1441214473
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
1441314474

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

14493+
wrap-ansi@^7.0.0:
14494+
version "7.0.0"
14495+
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
14496+
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
14497+
dependencies:
14498+
ansi-styles "^4.0.0"
14499+
string-width "^4.1.0"
14500+
strip-ansi "^6.0.0"
14501+
1443214502
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
1443314503
version "8.1.0"
1443414504
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"

0 commit comments

Comments
 (0)