From 36739d10530f4372e2d3885bc3ba4df4bea391df Mon Sep 17 00:00:00 2001 From: mattcasey Date: Wed, 25 Jun 2025 13:12:37 -0600 Subject: [PATCH 1/5] send logs to datadog over rest --- package.json | 2 ++ src/lib/log/logLevel.ts | 52 ++++++++---------------------------- src/lib/log/sendToDatadog.ts | 45 +++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 41 deletions(-) create mode 100644 src/lib/log/sendToDatadog.ts diff --git a/package.json b/package.json index 6b769e8b..cb429269 100644 --- a/package.json +++ b/package.json @@ -144,6 +144,8 @@ }, "dependencies": { "@datadog/browser-logs": "^6.1.0", + "@datadog/datadog-api-client": "^2.0.0-beta.1", + "@datadog/datadog-api-client-logs": "^0.0.1-beta.1", "@ethereum-attestation-service/eas-sdk": "^2.7.0", "@prisma/client": "^6.9.0", "async-sema": "^3.1.1", diff --git a/src/lib/log/logLevel.ts b/src/lib/log/logLevel.ts index fff1f0fa..88529c4f 100644 --- a/src/lib/log/logLevel.ts +++ b/src/lib/log/logLevel.ts @@ -1,21 +1,15 @@ import { datadogLogs } from '@datadog/browser-logs'; -import { RateLimit } from 'async-sema'; import type { Logger, LogLevelDesc } from 'loglevel'; import _log from 'loglevel'; import { isNodeEnv, isProdEnv, isStagingEnv } from '../../config/constants'; import { formatLog } from './logUtils'; +import { sendToDatadog } from './sendToDatadog'; -const ERRORS_WEBHOOK = - 'https://discord.com/api/webhooks/898365255703470182/HqS3KqH_7-_dj0KYR6EzNqWhkH0yX6kvV_P32sZ3gnvB8M4AyMoy7W9bbjIul3Hmyu98'; const originalFactory = _log.methodFactory; -const enableDiscordAlerts = isProdEnv && isNodeEnv; const enableDatadogLogs = isProdEnv && !isNodeEnv; -// requests per second = 35, timeUnit = 1sec -const discordRateLimiter = RateLimit(30); - /** * Enable formatting special logs for Datadog in production * Example: @@ -45,17 +39,22 @@ export function apply(log: Logger, logPrefix: string = '') { }); originalMethod.apply(null, args); - // post errors to Discord - if (isProdEnv && methodName === 'error' && enableDiscordAlerts) { - sendErrorToDiscord(ERRORS_WEBHOOK, message, opt).catch((error) => { - logErrorPlain('Error posting to discord', { originalMessage: message, error }); + if (isProdEnv) { + const args2 = formatLog(message, opt, { + formatLogsForDocker: false, // this option stringifies the log, which is not what we want + isNodeEnv, + logPrefix, + methodName }); + sendToDatadog(methodName, args2[0], args2[1]); } }; }; log.setLevel(log.getLevel()); // Be sure to call setLevel method in order to apply plugin - } else if (enableDatadogLogs) { + } + // send logs to Datadog in production from browser clients + else if (enableDatadogLogs) { log.methodFactory = (methodName, logLevel, loggerName) => { const originalMethod = originalFactory(methodName, logLevel, loggerName); return (message, ...args) => { @@ -82,32 +81,3 @@ function logErrorPlain(message: string, opts: any) { }) ); } - -async function sendErrorToDiscord(webhook: string, message: any, opt: any) { - const fields: { name: string; value?: string }[] = []; - // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires - // const http = require('../http'); - // if (opt instanceof Error) { - // fields = [ - // { name: 'Error', value: opt.message }, - // { name: 'Stacktrace', value: opt.stack?.slice(0, 500) } - // ]; - // } else if (opt) { - // fields = Object.entries(opt) - // .map(([name, _value]) => { - // const value = typeof _value === 'string' ? _value.slice(0, 500) : JSON.stringify(_value || {}); - // return { name, value }; - // }) - // .slice(0, 5); // add a sane max # of fields just in case - // } - // await discordRateLimiter(); - // return http.POST(webhook, { - // embeds: [ - // { - // color: 14362664, // #db2828 - // description: message, - // fields - // } - // ] - // }); -} diff --git a/src/lib/log/sendToDatadog.ts b/src/lib/log/sendToDatadog.ts new file mode 100644 index 00000000..2ff60052 --- /dev/null +++ b/src/lib/log/sendToDatadog.ts @@ -0,0 +1,45 @@ +/* eslint-disable no-console */ +import { createConfiguration } from '@datadog/datadog-api-client'; +import { LogsApiV2 } from '@datadog/datadog-api-client-logs'; + +type DatadogLogPayload = { + message: string; + level: string; + timestamp: number; + context?: string; + data?: any; + service: string; + ddsource: string; + ddtags: string; +}; + +const configuration = createConfiguration(); +const apiInstance = new LogsApiV2(configuration); + +const env = process.env.REACT_APP_APP_ENV || process.env.NODE_ENV || 'unknown'; + +export function sendToDatadog(level: string, log: string, context?: any) { + const logItem: DatadogLogPayload = { + service: process.env.DD_SERVICE || 'unknown', + ddsource: 'nodejs', + ddtags: `env:${env}`, + message: log, + context, + level, + timestamp: Date.now() + }; + + return ( + apiInstance + .submitLog({ + body: [logItem], + ddtags: `env:${env}` + }) + // .then((data) => { + // console.log(`API called successfully. Returned data: ${JSON.stringify(data)}`); + // }) + .catch((error) => { + console.error(`Error calling Datadog Logs API: ${error}`); + }) + ); +} From 605d1a4cf42a33df772a6bc3ef89f052292fecea Mon Sep 17 00:00:00 2001 From: Automated Version Bump Date: Wed, 25 Jun 2025 19:25:35 +0000 Subject: [PATCH 2/5] ci: version bump to 0.126.3-rc-datadog-logs.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cb429269..7d6ce61a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@charmverse/core", - "version": "0.126.2", + "version": "0.126.3-rc-datadog-logs.0", "description": "Core API for Charmverse", "type": "commonjs", "types": "./dist/cjs/index.d.ts", From 39ad96fa1ac35f94a244cf082a4d674d5259b181 Mon Sep 17 00:00:00 2001 From: Automated Version Bump Date: Wed, 25 Jun 2025 19:36:31 +0000 Subject: [PATCH 3/5] ci: version bump to 0.126.4-rc-datadog-logs.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 79ffcacf..2e13a717 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@charmverse/core", - "version": "0.126.3", + "version": "0.126.4-rc-datadog-logs.0", "description": "Core API for Charmverse", "type": "commonjs", "types": "./dist/cjs/index.d.ts", From cf65d52dd991e66b1007dc0fdbd4a5fc6ef61712 Mon Sep 17 00:00:00 2001 From: mattcasey Date: Wed, 25 Jun 2025 14:49:51 -0600 Subject: [PATCH 4/5] .. --- package.json | 2 -- src/lib/log/logLevel.ts | 4 +-- src/lib/log/logUtils.ts | 4 +-- src/lib/log/sendToDatadog.ts | 56 ++++++++++++++++++++++-------------- src/logTest.ts | 14 +++++++++ 5 files changed, 52 insertions(+), 28 deletions(-) create mode 100644 src/logTest.ts diff --git a/package.json b/package.json index 2e13a717..e240eb88 100644 --- a/package.json +++ b/package.json @@ -144,8 +144,6 @@ }, "dependencies": { "@datadog/browser-logs": "^6.1.0", - "@datadog/datadog-api-client": "^2.0.0-beta.1", - "@datadog/datadog-api-client-logs": "^0.0.1-beta.1", "@ethereum-attestation-service/eas-sdk": "^2.7.0", "@prisma/client": "^6.9.0", "async-sema": "^3.1.1", diff --git a/src/lib/log/logLevel.ts b/src/lib/log/logLevel.ts index 88529c4f..9629073b 100644 --- a/src/lib/log/logLevel.ts +++ b/src/lib/log/logLevel.ts @@ -41,8 +41,8 @@ export function apply(log: Logger, logPrefix: string = '') { if (isProdEnv) { const args2 = formatLog(message, opt, { - formatLogsForDocker: false, // this option stringifies the log, which is not what we want - isNodeEnv, + formatLogsForDocker: false, + isNodeEnv: false, logPrefix, methodName }); diff --git a/src/lib/log/logUtils.ts b/src/lib/log/logUtils.ts index 4f815a00..658af363 100644 --- a/src/lib/log/logUtils.ts +++ b/src/lib/log/logUtils.ts @@ -2,7 +2,7 @@ import { DateTime } from 'luxon'; export const TIMESTAMP_FORMAT = 'yyyy-LL-dd HH:mm:ss'; -type LogMeta = { +export type LogMeta = { data?: any; error?: { message: string; code?: number; stack?: string }; }; @@ -66,7 +66,7 @@ export function formatTime(date: DateTime) { } // Check if value is primitive value -function _isPrimitiveValue(value: unknown): boolean { +export function _isPrimitiveValue(value: unknown): boolean { return ( typeof value === 'symbol' || typeof value === 'string' || diff --git a/src/lib/log/sendToDatadog.ts b/src/lib/log/sendToDatadog.ts index 2ff60052..20f9ff44 100644 --- a/src/lib/log/sendToDatadog.ts +++ b/src/lib/log/sendToDatadog.ts @@ -1,6 +1,5 @@ /* eslint-disable no-console */ -import { createConfiguration } from '@datadog/datadog-api-client'; -import { LogsApiV2 } from '@datadog/datadog-api-client-logs'; +import { _isPrimitiveValue, type LogMeta } from './logUtils'; type DatadogLogPayload = { message: string; @@ -13,33 +12,46 @@ type DatadogLogPayload = { ddtags: string; }; -const configuration = createConfiguration(); -const apiInstance = new LogsApiV2(configuration); - const env = process.env.REACT_APP_APP_ENV || process.env.NODE_ENV || 'unknown'; +const service = process.env.DD_SERVICE || 'unknown'; + +const ddtags = `env:${env}`; + +export async function sendToDatadog(level: string, log: string, context?: any) { + if (!process.env.DD_API_KEY) { + return; + } + + let error: LogMeta['error'] = (context as LogMeta | undefined)?.error; + const maybeError = (context as { error?: Error })?.error || context; + if (maybeError instanceof Error) { + error = { ...maybeError, message: maybeError.message, stack: maybeError.stack }; + } + if (_isPrimitiveValue(context) || context instanceof Array) { + context = { data: context }; + } -export function sendToDatadog(level: string, log: string, context?: any) { const logItem: DatadogLogPayload = { - service: process.env.DD_SERVICE || 'unknown', ddsource: 'nodejs', - ddtags: `env:${env}`, + ...context, message: log, - context, + error, + hostName: process.env.HOSTNAME, // defined in beanstalk + service, + ddtags, level, timestamp: Date.now() }; - return ( - apiInstance - .submitLog({ - body: [logItem], - ddtags: `env:${env}` - }) - // .then((data) => { - // console.log(`API called successfully. Returned data: ${JSON.stringify(data)}`); - // }) - .catch((error) => { - console.error(`Error calling Datadog Logs API: ${error}`); - }) - ); + const response = await fetch('https://http-intake.logs.datadoghq.com/api/v2/logs', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'DD-API-KEY': process.env.DD_API_KEY + }, + body: JSON.stringify(logItem) + }); + if (!response.ok) { + console.error(`Error sending log to Datadog: ${response.status} - ${response.statusText}`); + } } diff --git a/src/logTest.ts b/src/logTest.ts new file mode 100644 index 00000000..a446e896 --- /dev/null +++ b/src/logTest.ts @@ -0,0 +1,14 @@ +import { log } from './lib/log/log'; + +function main() { + log.info('test'); + log.error('test', { error: new Error('test error') }); + log.warn('test', new Error('test error inline')); + log.debug('test', ['test', 'test2']); + log.trace('test', 'another string'); + setTimeout(() => { + process.exit(0); + }, 1000); +} + +main(); From 9c8240d50d33be8e9ac1008b2a8732feb4187cfa Mon Sep 17 00:00:00 2001 From: Automated Version Bump Date: Wed, 25 Jun 2025 20:51:43 +0000 Subject: [PATCH 5/5] ci: version bump to 0.126.4-rc-datadog-logs.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e240eb88..7abf88fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@charmverse/core", - "version": "0.126.4-rc-datadog-logs.0", + "version": "0.126.4-rc-datadog-logs.1", "description": "Core API for Charmverse", "type": "commonjs", "types": "./dist/cjs/index.d.ts",