Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/soft-tools-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/hydrogen': patch
---

Custom loggers can return promises from their methods. Hydrogen will await for them after the current request is over but before the runtime instance ends.
7 changes: 6 additions & 1 deletion docs/framework/hydrogen-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,12 @@ export default defineConfig({
/* Overrides the default `log.trace` behavior. */
trace: (request, ...args) => console.log(request.url, ...args),
/* Overrides the default `log.error` behavior. */
error: (request, error) => myErrorTrackingService.send(error, {request}),
error: async (request, error) => {
console.error(error);
// Methods can return promises. Hydrogen won't block the current
// request but it will await for them before the runtime instance ends.
await myErrorTrackingService.send(request, error);
},
/* ... */

/* Logs the cache status of each stored entry: `PUT`, `HIT`, `MISS` or `STALE`. */
Expand Down
23 changes: 21 additions & 2 deletions packages/hydrogen/src/utilities/log/__tests__/log.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,16 +159,35 @@ describe('log', () => {
});

it('gets logger for a given context', () => {
const clog = getLoggerWithContext({some: 'data'});
const clog = getLoggerWithContext({url: 'example.com'});

(clog as any)[method](`hydrogen: ${method}`);
expect((mockLogger as any)[method]).toHaveBeenCalled();
expect(((mockLogger as any)[method] as any).mock.calls[0][0]).toEqual({
some: 'data',
url: 'example.com',
});
expect(((mockLogger as any)[method] as any).mock.calls[0][1]).toBe(
`hydrogen: ${method}`
);
});

it('marks async calls for waitUntil', () => {
const waitUntilPromises = [] as Array<Promise<any>>;

const clog = getLoggerWithContext({
ctx: {
runtime: {waitUntil: (p: Promise<any>) => waitUntilPromises.push(p)},
} as unknown as ServerComponentRequest['ctx'],
});

(clog as any)[method]('no promise 1');
(clog as any)[method]('no promise 2');
expect(waitUntilPromises).toHaveLength(0);

setLogger({[method]: async () => null});
(clog as any)[method]('promise 1');
(clog as any)[method]('promise 2');
expect(waitUntilPromises).toHaveLength(2);
});
});
});
36 changes: 25 additions & 11 deletions packages/hydrogen/src/utilities/log/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import {parseUrl} from './utils';
* current request in progress.
*/

type LoggerMethod = (...args: Array<any>) => void | Promise<any>;
export interface Logger {
trace: (...args: Array<any>) => void;
debug: (...args: Array<any>) => void;
warn: (...args: Array<any>) => void;
error: (...args: Array<any>) => void;
fatal: (...args: Array<any>) => void;
trace: LoggerMethod;
debug: LoggerMethod;
warn: LoggerMethod;
error: LoggerMethod;
fatal: LoggerMethod;
options: () => LoggerOptions;
}

Expand Down Expand Up @@ -51,13 +52,26 @@ const defaultLogger: Logger = {

let currentLogger = defaultLogger as Logger;

export function getLoggerWithContext(context: any): Logger {
function doLog(
method: keyof typeof defaultLogger,
request: Partial<ServerComponentRequest>,
...args: any[]
) {
const maybePromise = currentLogger[method](request, ...args);
if (maybePromise instanceof Promise) {
request?.ctx?.runtime?.waitUntil?.(maybePromise);
}
}

export function getLoggerWithContext(
context: Partial<ServerComponentRequest>
): Logger {
return {
trace: (...args) => currentLogger.trace(context, ...args),
debug: (...args) => currentLogger.debug(context, ...args),
warn: (...args) => currentLogger.warn(context, ...args),
error: (...args) => currentLogger.error(context, ...args),
fatal: (...args) => currentLogger.fatal(context, ...args),
trace: (...args) => doLog('trace', context, ...args),
debug: (...args) => doLog('debug', context, ...args),
warn: (...args) => doLog('warn', context, ...args),
error: (...args) => doLog('error', context, ...args),
fatal: (...args) => doLog('fatal', context, ...args),
options: () => currentLogger.options(),
};
}
Expand Down