Using pino with nextjs
Closed this issue · 2 comments
charanjit-singh commented
Hey,
Thank you for this wonderful project!
I want to integrate pino with nextjs with the following requirements.
- Support of correlation id (from cookies)
- Support of request-id from middleware (or vercel)
- Support of logflare
- Support of a single
logger
file to use in client and server side.
Expectations for import is
// Server Side
import logger from '@/utils/logger';
export default Page () {
logger.info("Page Loaded");
...
}
"use client";
import logger from '@/utils/logger';
export default Component () {
logger.info("Component Loaded");
...
}
And if there is common function that can be used in both client side and server side:
import logger from '@/utils/logger';
const commonFunction () {
logger.info("Common function Callled");
}
Here's my baseLogger.ts
import pino from 'pino';
import { createPinoBrowserSend, createWriteStream } from "pino-logflare"
const isPreviewOrProduction = process.env.VERCEL_ENV === 'preview' || process.env.VERCEL_ENV === 'production';
// Log to Logflare only in production or preview
let baseLogger: pino.Logger;
if (isPreviewOrProduction && process.env.NEXT_PUBLIC_LOGFLARE_KEY && process.env.NEXT_PUBLIC_LOGFLARE_SOURCE) {
const stream = createWriteStream({
apiKey: process.env.NEXT_PUBLIC_LOGFLARE_KEY,
sourceToken: process.env.NEXT_PUBLIC_LOGFLARE_SOURCE,
});
const send = createPinoBrowserSend({
apiKey: process.env.NEXT_PUBLIC_LOGFLARE_KEY,
sourceToken: process.env.NEXT_PUBLIC_LOGFLARE_SOURCE,
});
baseLogger = pino({
browser: {
transmit: {
level: "info",
send: send,
}
},
level: "debug",
base: {
env: process.env.VERCEL_ENV || process.env.NODE_ENV,
release: process.env.NEXT_PUBLIC_APP_VERSION,
},
}, stream);
} else {
baseLogger = pino({
level: "debug",
base: {
env: process.env.VERCEL_ENV || process.env.NODE_ENV,
release: process.env.NEXT_PUBLIC_APP_VERSION,
},
});
}
export default baseLogger;
and here's logger/index.ts
import baseLogger from "./baseLogger";
import { posthogCookieName } from "./constants";
let logger = baseLogger;
// Check if browser or server
if (typeof window !== "undefined") {
// Browser (client)
// Get cookie from browser
const posthog = document.cookie
.split("; ")
.find((row) => row.startsWith(`${posthogCookieName}=`));
const value = JSON.parse(posthog?.split("=")[1] || "{}");
const distinctId = value?.distinct_id;
logger = baseLogger.child({
from: "browser",
correlationId: distinctId,
});
} else {
// Server
const {
cookies,
headers
} = require("next/headers");
const posthog = cookies().get(posthogCookieName);
const value = JSON.parse(posthog?.value || "{}");
const distinctId = value?.distinct_id;
const requestId = headers().get("x-vercel-id");
logger = baseLogger.child({
from: "server",
correlationId: distinctId,
requestId,
});
}
export default logger;
Now the logger/index.ts
, when imported in nextjs client side is rendering it on server, not client.
What are the options here?
charanjit-singh commented
NVM I did this and it's working
import { NextRequest } from "next/server";
import baseLogger from "./baseLogger";
import { posthogCookieName } from "./constants";
const getLogger = (name: string, req?: NextRequest) => {
if (typeof window !== "undefined") {
// Browser (client)
// Get cookie from browser
const cookies = document.cookie;
const cookieKeyValues = cookies.split(";").map((cookie) => cookie.trim().split("="));
const posthog = cookieKeyValues.find(([key]) => key === posthogCookieName);
let correlationId = null;
if (posthog) {
try {
const posthookCookieValue = posthog[1];
const value = JSON.parse(decodeURIComponent(posthookCookieValue));
correlationId = value?.distinct_id;
} catch (error) {
console.error("Error parsing posthog cookie", error);
}
}
const windowLocation = window.location;
const windowQueryParams = new URLSearchParams(windowLocation.search)
const queryParamsObj = Object.fromEntries(windowQueryParams.entries());
return baseLogger.child({
name,
from: "browser",
correlationId,
window: {
location: windowLocation.href,
search: queryParamsObj,
}
});
} else {
// Server Side
// Check if edge runtime
// @ts-ignore
const isEdgeRuntime = typeof EdgeRuntime !== 'undefined';
if (isEdgeRuntime) {
if (!req) {
throw new Error('Request object is required in edge runtime');
}
const requestId = req?.headers.get("x-vercel-id");
let headersJson = Object.fromEntries(req.headers.entries()) || {};
// All headers except cookie
delete headersJson['cookie']
const posthog = req.cookies.get(posthogCookieName)
const value = JSON.parse(posthog?.value || '{}');
const distinctId = value?.distinct_id
const nextUrl = req.nextUrl;
const logger = baseLogger.child({
requestId,
correlationId: distinctId,
url: nextUrl.href,
method: req.method,
headers: headersJson,
cookies: req.cookies.getAll(),
geo: req.geo,
from: "edge",
name,
});
return logger;
} else {
const {
cookies,
headers,
} = require("next/headers");
const posthog = cookies().get(posthogCookieName);
const value = JSON.parse(posthog?.value || "{}");
const distinctId = value?.distinct_id;
const requestId = headers().get("x-vercel-id");
return baseLogger.child({
name,
from: "server",
correlationId: distinctId,
requestId,
});
}
}
}
export default getLogger;
In middleware:
const logger = getLogger('middleware', request)
Client side:
const logger = getLogger('src/app/(website)/_components/Navbar.tsx');
github-actions commented
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.