-
-
Save justjake/6fa39b07421a58ad58ff013b141b7172 to your computer and use it in GitHub Desktop.
// @ts-check | |
"use strict" | |
/** | |
* Set up datadog tracing. This should be called first, so Datadog can hook | |
* all the other dependencies like `http`. | |
*/ | |
function setUpDatadogTracing() { | |
const { tracer: Tracer } = require('dd-trace') | |
const tracer = Tracer.init({ | |
// Your options here. | |
runtimeMetrics: true, | |
logInjection: true, | |
}) | |
} | |
/** | |
* Polyfill DOMParser for react-intl | |
* Otherwise react-intl spews errors related to formatting | |
* messages with <xml>in them</xml> | |
*/ | |
function setUpDOMParser() { | |
const xmldom = require("xmldom") | |
global["DOMParser"] = xmldom.DOMParser | |
} | |
/** | |
* Set up logging. Monkey patches a bunch of stuff. | |
*/ | |
function setUpLogging() { | |
// pino is a simple JSON logger with Datadog integration. | |
// By default it logs to STDOUT. | |
const pino = require('pino') | |
const logger = pino({ | |
// Your options here. | |
}) | |
function getLoggingFunction(/** @type {string} */ levelName) { | |
const baseLogFn = (logger[levelName] || logger.info).bind(logger) | |
return function patchedLog(/** @type {any[]} */ ...parts) { | |
/** @type {object | undefined} */ | |
let data = undefined | |
/** @type {object | undefined} */ | |
let error = undefined | |
/** @type {object | undefined} */ | |
const nativeError = parts.find( | |
it => | |
(it && it instanceof Error) || | |
(it && typeof it === "object" && "name" in it && "message" in it) | |
) | |
if (nativeError) { | |
error = cleanObjectForSerialization(nativeError) | |
// If you use Sentry, Rollbar, etc, you could capture the error here. | |
// ErrorThingy.report(nativeError) | |
} | |
// If next is trying to log funky stuff, put it into the data object. | |
if (parts.length > 1) { | |
data = data || {} | |
data.parts = parts.map(part => cleanObjectForSerialization(part)) | |
} | |
const messages = | |
nativeError && parts.length === 1 ? [nativeError.toString()] : parts | |
baseLogFn({ data, error, type: levelName }, ...messages) | |
} | |
} | |
// Monkey-patch Next.js logger. | |
// See https://github.com/atkinchris/next-logger/blob/main/index.js | |
// See https://github.com/vercel/next.js/blob/canary/packages/next/build/output/log.ts | |
const nextBuiltInLogger = require("next/dist/build/output/log") | |
for (const [property, value] of Object.entries(nextBuiltInLogger)) { | |
if (typeof value !== "function") { | |
continue | |
} | |
nextBuiltInLogger[property] = getLoggingFunction(property) | |
} | |
/** | |
* Monkey-patch global console.log logger. Yes. Sigh. | |
* @type {Array<keyof typeof console>} | |
*/ | |
const loggingProperties = ["log", "debug", "info", "warn", "error"] | |
for (const property of loggingProperties) { | |
console[property] = getLoggingFunction(property) | |
} | |
// Add general error logging. | |
process.on("unhandledRejection", (error, promise) => { | |
logger.error( | |
{ | |
type: "unhandledRejection", | |
error: cleanObjectForSerialization(error), | |
data: { promise: cleanObjectForSerialization(promise) }, | |
}, | |
`${error}` | |
) | |
}) | |
process.on("uncaughtException", error => { | |
logger.error( | |
{ type: "uncaughtException", error: cleanObjectForSerialization(error) }, | |
`${error}` | |
) | |
}) | |
} | |
function cleanObjectForSerialization(value) { | |
// Clean up or copy `value` so our logger or error reporting system | |
// can record it. | |
// | |
// Because our logger `pino` uses JSON.stringify, we need to do | |
// the following here: | |
// | |
// 1. Remove all cycles. JSON.stringify throws an error when you pass | |
// a value with cyclical references. | |
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value | |
// 2. Because JSON.stringify only serializes enumerable properties, we | |
// need to copy interesting, but non-enumerable properties like | |
// value.name and value.message for errors: | |
// JSON.stringify(new Error('nothing serialized')) returns '{}' | |
// | |
// Implementing this correctly is beyond the scope of my example. | |
return value | |
} | |
setUpDatadogTracing() | |
setUpDOMParser() | |
setUpLogging() |
- You can read the Nextjs source code to find out how Nextjs handles requests
- You can read the dd-trace source code to find out how Datadog hooks libraries to make them inspectable. For example: https://github.com/DataDog/dd-trace-js/blob/master/packages/datadog-plugin-http/src/server.js
thanks jake
Be careful though to not use pino-pretty
this way, at least not as how leerob is suggesting here: vercel/next.js#32215 (comment) since next dev
or next build
will create too many instances of pino
for its workers. This will lead to a huge memory consumption and is not workable.
Imagine:
const logger = pino({
name: 'my-storefront-logger',
level: 'debug',
transport: {
target: 'pino-pretty',
options: {
translateTime: 'yyyy-mm-dd HH:MM:ss.l o',
sync: true,
},
},
});
and then start a next dev
like this:
$ NODE_OPTIONS='-r ./server-preload.js' node ../node_modules/.bin/next dev
@y-a-v-a thanks for the heads up, much appreciated 🍻
Hi @justjake,
This is just what I need so thank you! However, I'm currently getting the below error when running it, any ideas how to resolve this please?
nextBuiltInLogger[property] = getLoggingFunction(property)
^
TypeError: Cannot set property wait of # which has only a getter
at setUpLogging (C:...logger.js:58:33)
at Object. (C:...logger.js:109:1)
at Module._compile (node:internal/modules/cjs/loader:1241:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1295:10)
at Module.load (node:internal/modules/cjs/loader:1091:32)
at Module._load (node:internal/modules/cjs/loader:938:12)
at internalRequire (node:internal/modules/cjs/loader:166:19)
at Module._preloadModules (node:internal/modules/cjs/loader:1420:5)
at loadPreloadModules (node:internal/process/pre_execution:705:5)
at setupUserModules (node:internal/process/pre_execution:170:3)
@bradleyberwick it’s been 3 years so things changed in NextJS. It seems like the wait
property of next built in logger object isn’t writable anymore, so you’ll need to change the code to avoid trying to set that property, or use Object.defineProperty. You can find answers about the logger object by reading the NextJS source code.
For anyone else coming around this, we upgrade Next from 13 to 14 and started seeing that same error.
next-logger
source code has somewhat a solution for it here. Or you can just use next-logger
altogether 👍
Hi @justjake!
I have general logging question on top of your write up. How do we grab hold of incoming request, for unhandled promise rejections or unhandled errors events, that say happen somewhere in our
getServerSideProps
code, because of an incoming request? Wouldn't it be a good idea to be able to associate request meta data with those unhandled promise rejections or unhandled errors?I've never implemented logging in any application before and am trying to wrap my head around what data logged where helps in tracing something having gone wrong. I couldn't find any production code or resources which would help me find some of these answers. If you could direct me to some that you might know that'd be of help.