pinojs/pino

Using pino with nextjs

Closed this issue · 2 comments

Hey,

Thank you for this wonderful project!

I want to integrate pino with nextjs with the following requirements.

  1. Support of correlation id (from cookies)
  2. Support of request-id from middleware (or vercel)
  3. Support of logflare
  4. 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?

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');

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.