Skip to content

Commit

Permalink
Merge pull request #90 from haydn/add-support-for-server-side-props
Browse files Browse the repository at this point in the history
Add a HOF for wrapping getServerSideProps handlers
  • Loading branch information
schehata committed Feb 9, 2023
2 parents 8f274f1 + e55bbc3 commit 0b17e82
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 17 deletions.
16 changes: 16 additions & 0 deletions README.md
Expand Up @@ -127,6 +127,22 @@ You can also disable logging completely by setting the log level to `off`:
export AXIOM_LOG_LEVEL=off
```

### getServerSideProps

To be able to use next-axiom with `getServerSideProps` you need to wrap your function with `withAxiomGetServerSideProps`, becasue there is no
way at the moment to automatically detected if getServerSideProps is used.

```ts
import { withAxiomGetServerSideProps } from 'next-axiom'
export const getServerSideProps = withAxiomGetServerSideProps(async ({ req, log }) => {
log.info('Hello, world!');
return {
props: {
},
}
});


### License

Distributed under the [MIT License](LICENSE).
28 changes: 25 additions & 3 deletions __tests__/withAxiom.test.ts
@@ -1,5 +1,6 @@
import { withAxiom } from '../src/index';
import { NextApiRequest, NextApiResponse } from 'next';
import { Logger, withAxiom } from '../src/index';
import { withAxiomGetServerSideProps, withAxiomNextServerSidePropsHandler } from '../src/withAxiom';
import { GetServerSideProps, GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next';
import 'whatwg-fetch';
import { NextFetchEvent, NextRequest, NextResponse } from 'next/server';

Expand All @@ -17,6 +18,16 @@ test('withAxiom(NextApiHandler)', async () => {
expect(handler).toBeInstanceOf(Function);
});

test('withAxiomNextServerSidePropsHandler', async () => {
const handler = withAxiomNextServerSidePropsHandler(async (context) => {
expect(context.log).toBeInstanceOf(Logger);
return {
props: {},
};
});
expect(handler).toBeInstanceOf(Function);
});

test('withAxiom(NextMiddleware)', async () => {
process.env.LAMBDA_TASK_ROOT = 'lol'; // shhh this is AWS Lambda, I promise
const handler = withAxiom((_req: NextRequest, _ev: NextFetchEvent) => {
Expand All @@ -43,5 +54,16 @@ test('withAxiom(NextConfig) with fallback rewrites (regression test for #21)', a
const config = withAxiom({
rewrites: rewrites as any,
});
await config.rewrites();
if (config.rewrites) await config.rewrites();
});

test('withAxiom(GetServerSideProps)', async () => {
const getServerSideProps: GetServerSideProps = async (context: GetServerSidePropsContext) => {
return {
props: {},
};
};
const handler = withAxiomGetServerSideProps(getServerSideProps);
expect(handler).toBeInstanceOf(Function);
// TODO: Make sure we have a AxiomGetServerSideProps
});
18 changes: 18 additions & 0 deletions examples/logger/pages/serverSideProps.tsx
@@ -0,0 +1,18 @@
import { withAxiomGetServerSideProps } from 'next-axiom';

export const getServerSideProps = withAxiomGetServerSideProps(async ({ req, log }) => {
log.info('Hello from server side');
return {
props: {
method: req.method,
},
};
});

export default function Home({ method }: { method: string }) {
return (
<div>
<h1>Hello from server, this is a {method} request</h1>
</div>
);
}
11 changes: 10 additions & 1 deletion src/index.ts
@@ -1,3 +1,12 @@
export { reportWebVitals } from './webVitals';
export { log, Logger } from './logger';
export { withAxiom, AxiomAPIRequest, AxiomRequest } from './withAxiom';
export {
withAxiom,
withAxiomGetServerSideProps,
AxiomAPIRequest,
AxiomRequest,
AxiomGetServerSideProps,
AxiomApiHandler,
AxiomMiddleware,
AxiomGetServerSidePropsContext,
} from './withAxiom';
6 changes: 3 additions & 3 deletions src/platform/generic.ts
@@ -1,4 +1,4 @@
import { NextApiRequest } from "next";
import { GetServerSidePropsContext, NextApiRequest } from "next";
import { LogEvent, RequestReport } from "../logger";
import { EndpointType } from "../shared";
import type Provider from "./base";
Expand Down Expand Up @@ -50,7 +50,7 @@ export default class GenericConfig implements Provider {
};
}

generateRequestMeta(req: NextApiRequest): RequestReport {
generateRequestMeta(req: NextApiRequest | GetServerSidePropsContext['req']): RequestReport {
return {
startTime: new Date().getTime(),
path: req.url!,
Expand All @@ -63,7 +63,7 @@ export default class GenericConfig implements Provider {
};
}

getHeaderOrDefault(req: NextApiRequest, headerName: string, defaultValue: any) {
getHeaderOrDefault(req: NextApiRequest | GetServerSidePropsContext['req'], headerName: string, defaultValue: any) {
return req.headers[headerName] ? req.headers[headerName] : defaultValue;
}
}
64 changes: 54 additions & 10 deletions src/withAxiom.ts
@@ -1,6 +1,16 @@
import { NextConfig, NextApiHandler, NextApiResponse, NextApiRequest } from 'next';
import {
NextConfig,
NextApiHandler,
NextApiResponse,
NextApiRequest,
GetServerSideProps,
GetServerSidePropsContext,
PreviewData,
GetServerSidePropsResult,
} from 'next';
import { NextFetchEvent, NextMiddleware, NextRequest } from 'next/server';
import { NextMiddlewareResult } from 'next/dist/server/web/types';
import { ParsedUrlQuery } from 'querystring';
import { Logger, RequestReport } from './logger';
import { Rewrite } from 'next/dist/lib/load-custom-routes';
import { EndpointType } from './shared';
Expand All @@ -10,7 +20,7 @@ declare global {
var EdgeRuntime: string;
}

function withAxiomNextConfig(nextConfig: NextConfig): NextConfig {
export function withAxiomNextConfig(nextConfig: NextConfig): NextConfig {
return {
...nextConfig,
rewrites: async () => {
Expand Down Expand Up @@ -102,7 +112,7 @@ export type AxiomApiHandler = (
response: NextApiResponse
) => NextApiHandler | Promise<NextApiHandler> | Promise<void>;

function withAxiomNextApiHandler(handler: NextApiHandler): NextApiHandler {
export function withAxiomNextApiHandler(handler: AxiomApiHandler): NextApiHandler {
return async (req, res) => {
const report: RequestReport = config.generateRequestMeta(req);
const logger = new Logger({}, report, false, 'lambda');
Expand All @@ -124,13 +134,43 @@ function withAxiomNextApiHandler(handler: NextApiHandler): NextApiHandler {
};
}

export type AxiomGetServerSidePropsContext<
Q extends ParsedUrlQuery = ParsedUrlQuery,
D extends PreviewData = PreviewData
> = GetServerSidePropsContext<Q, D> & { log: Logger };
export type AxiomGetServerSideProps<
P extends { [key: string]: any } = { [key: string]: any },
Q extends ParsedUrlQuery = ParsedUrlQuery,
D extends PreviewData = PreviewData
> = (context: AxiomGetServerSidePropsContext<Q, D>) => Promise<GetServerSidePropsResult<P>>;

export function withAxiomNextServerSidePropsHandler(handler: AxiomGetServerSideProps): GetServerSideProps {
return async (context) => {
const report: RequestReport = config.generateRequestMeta(context.req);
const logger = new Logger({}, report, false, 'lambda');
const axiomContext = context as AxiomGetServerSidePropsContext;
axiomContext.log = logger;

try {
const result = await handler(axiomContext);
await logger.flush();
return result;
} catch (error: any) {
logger.error('Error in getServerSideProps handler', { error });
logger.attachResponseStatus(500);
await logger.flush();
throw error;
}
};
}

export type AxiomRequest = NextRequest & { log: Logger };
export type AxiomMiddleware = (
request: AxiomRequest,
event: NextFetchEvent
) => NextMiddlewareResult | Promise<NextMiddlewareResult>;

function withAxiomNextEdgeFunction(handler: NextMiddleware): NextMiddleware {
export function withAxiomNextEdgeFunction(handler: NextMiddleware): NextMiddleware {
return async (req, ev) => {
const report: RequestReport = {
startTime: new Date().getTime(),
Expand Down Expand Up @@ -171,13 +211,13 @@ function logEdgeReport(report: any) {
}
}

type WithAxiomParam = NextConfig | NextApiHandler | NextMiddleware;
type WithAxiomParam = NextConfig | AxiomApiHandler | NextMiddleware;

function isNextConfig(param: WithAxiomParam): param is NextConfig {
return typeof param == 'object';
}

function isApiHandler(param: WithAxiomParam): param is NextApiHandler {
function isApiHandler(param: WithAxiomParam): param is AxiomApiHandler {
const isFunction = typeof param == 'function';

// Vercel defines EdgeRuntime for edge functions, but Netlify defines NEXT_RUNTIME = 'edge'
Expand All @@ -187,12 +227,16 @@ function isApiHandler(param: WithAxiomParam): param is NextApiHandler {
// withAxiom can be called either with NextConfig, which will add proxy rewrites
// to improve deliverability of Web-Vitals and logs, or with NextApiRequest or
// NextMiddleware which will automatically log exceptions and flush logs.
export function withAxiom<T extends WithAxiomParam>(param: T): T {
export function withAxiom(param: NextConfig): NextConfig;
export function withAxiom(param: AxiomApiHandler): NextApiHandler;
export function withAxiom(param: NextMiddleware): NextMiddleware;
export function withAxiom(param: WithAxiomParam) {
if (isNextConfig(param)) {
return withAxiomNextConfig(param) as T;
return withAxiomNextConfig(param);
} else if (isApiHandler(param)) {
return withAxiomNextApiHandler(param) as T;
return withAxiomNextApiHandler(param);
} else {
return withAxiomNextEdgeFunction(param) as T;
return withAxiomNextEdgeFunction(param);
}
}
export const withAxiomGetServerSideProps = withAxiomNextServerSidePropsHandler;

1 comment on commit 0b17e82

@vercel
Copy link

@vercel vercel bot commented on 0b17e82 Feb 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

next-axiom-example – ./

next-axiom-example-lemon.vercel.app
next-axiom-example.axiom.dev
next-axiom-example-git-main.axiom.dev

Please sign in to comment.