-
Notifications
You must be signed in to change notification settings - Fork 277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PoC: Add a logging abstraction and wrap remix build output to track errors #941
Conversation
We detected some changes in |
packages/remix-oxygen/src/server.ts
Outdated
type AppLoadContext, | ||
type ServerBuild, | ||
} from '@remix-run/server-runtime'; | ||
import type {Logger} from '@shopify/hydrogen'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CI is failing because of this. I think our build should be more robust, so that our packages can rely on shared types across the monorepo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe need to update turbo.json
to ensure that hydrogen
package is built before the remix-oxygen
package is built?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just copy the type? I think it would be better if remix-oxygen doesn't depend on Hydrogen... since it's just a generic adapter for Remix <> Oxygen 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, it might be best to keep them separate
const handleRequest = createRemixRequestHandler(build, mode); | ||
const routes: typeof build.routes = {}; | ||
|
||
for (const [id, route] of Object.entries(build.routes)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where remix modules loader and actions are overwritten
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if it's the job of remix-oxygen to wrap the Remix build and enhance it with logs.
Maybe we could also consider exporting this as a standalone function from Hydrogen itself?
Perhaps as part of createLogger
(as an option, you pass the Remix build to enhance its logs), or as a separate utility?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was only hoping to automatically add logging for existing merchants without having them update their server.ts. The only way to do that AFAIK is to do it in the oxygen handler. Though maybe that's not a fair tradeoff considering no one outside Oxygen would get it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@frandiox are you suggesting:
const log = new Logger({ request, waitUntil, remixBuild });
And then having the logger mutate remixBuild.routes
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either that or as a separate utility, yes. Looking at it now it doesn't feel like it's clear what it does... so maybe a more verbose function name would be better?
packages/hydrogen/src/logger.ts
Outdated
if (error instanceof Error) { | ||
console.error(red(`Error processing route:${url}\n${error.stack}`)); | ||
} else { | ||
console.error(red(`Error:${url} ${error}`)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I copied this logic from H1, though not sure if we want to keep it 🤷🏼
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this is where we implement the logging format that we discussed in #876?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I copied this logic from H1, though not sure if we want to keep it 🤷🏼
Maybe we can remove it for now?
I wonder if this is where we implement the logging format that we discussed in 876
I think that would be a different place since that should only be for Hydrogen logs. This logger here is supposed to be used manually by the user as well, right?
@@ -31,6 +47,23 @@ export function createRequestHandler<Context = unknown>({ | |||
}; | |||
} | |||
|
|||
type RemixModule = ServerBuild['routes'][0]['module']; | |||
|
|||
function fill(module: RemixModule, name: 'loader' | 'action', log?: Logger) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it also support handle
and meta
exports?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice that the skeleton app passes in a Logger instance and the demo store does not. This just verifies that it works with and without the logger passed in. If it isn't passed in, just default to console.error().
I feel like it should be the other way around - implemented in demo-store but not in skeleton? Not a big deal though.
|
||
export class Logger { | ||
#waitUntil: (p: Promise<unknown>) => void; | ||
#request: Request; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh baby private fields on a class. We fancy now.
type LoggerMethod = ( | ||
context: {request: Request}, | ||
...args: Array<any> | ||
) => void | Promise<any>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Promise<void>
or do we actually expect to get something out of the promise here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably void
is more correct
); | ||
} | ||
} catch (e) { | ||
const message = e instanceof Error ? e.stack : e; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the message
is from e.stack
? or should it be from e.message
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the stack normally contains the same message, so this is just printing the whole thing. Might be wrong tho.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the stack contains the message.
packages/hydrogen/src/logger.ts
Outdated
if (error instanceof Error) { | ||
console.error(red(`Error processing route:${url}\n${error.stack}`)); | ||
} else { | ||
console.error(red(`Error:${url} ${error}`)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this is where we implement the logging format that we discussed in #876?
packages/remix-oxygen/src/server.ts
Outdated
type AppLoadContext, | ||
type ServerBuild, | ||
} from '@remix-run/server-runtime'; | ||
import type {Logger} from '@shopify/hydrogen'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe need to update turbo.json
to ensure that hydrogen
package is built before the remix-oxygen
package is built?
packages/hydrogen/src/logger.ts
Outdated
@@ -0,0 +1,103 @@ | |||
import {yellow, red} from 'kolorist'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer not to bundle kolorist or any library like that in Hydrogen itself. We can just add colors from the CLI in dev by recognizing patterns 🤔
waitUntil: (p: Promise<unknown>) => void, | ||
request: Request, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe request
should be the first argument? waitUntil
could be optional in some environments like Node.js
); | ||
} | ||
} catch (e) { | ||
const message = e instanceof Error ? e.stack : e; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the stack normally contains the same message, so this is just printing the whole thing. Might be wrong tho.
trace: (context, ...args) => { | ||
console.log(...args); | ||
}, | ||
|
||
debug: (context, ...args) => { | ||
console.log(...args); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these default to console.trace
and console.debug
respectively?
packages/hydrogen/src/logger.ts
Outdated
if (error instanceof Error) { | ||
console.error(red(`Error processing route:${url}\n${error.stack}`)); | ||
} else { | ||
console.error(red(`Error:${url} ${error}`)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I copied this logic from H1, though not sure if we want to keep it 🤷🏼
Maybe we can remove it for now?
I wonder if this is where we implement the logging format that we discussed in 876
I think that would be a different place since that should only be for Hydrogen logs. This logger here is supposed to be used manually by the user as well, right?
packages/remix-oxygen/src/server.ts
Outdated
type AppLoadContext, | ||
type ServerBuild, | ||
} from '@remix-run/server-runtime'; | ||
import type {Logger} from '@shopify/hydrogen'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just copy the type? I think it would be better if remix-oxygen doesn't depend on Hydrogen... since it's just a generic adapter for Remix <> Oxygen 🤔
const handleRequest = createRemixRequestHandler(build, mode); | ||
const routes: typeof build.routes = {}; | ||
|
||
for (const [id, route] of Object.entries(build.routes)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if it's the job of remix-oxygen to wrap the Remix build and enhance it with logs.
Maybe we could also consider exporting this as a standalone function from Hydrogen itself?
Perhaps as part of createLogger
(as an option, you pass the Remix build to enhance its logs), or as a separate utility?
@blittle one more thing to keep in mind: the |
@blittle I've pushed a few commits mostly to show that we should also wrap deferred data because Remix also swallows that. I've also removed type and colors dependencies. Still not sure if this should happen in |
Given that the only extensibility point is the logger method, it requires a more advanced composition pattern (we used higher-order functions) if you want to e.g. send any log statement to an external source. Have you considered a more generic extension point that would be called for all logger methods? Also, have you considered introducing a |
@davidhousedev this is an early PoC, and we'd love some ideas. I'm happy to add a |
@frandiox we might be able to simplify this given this change to Remix: remix-run/remix#6495 |
@blittle closing this PR due to inactivity. @frandiox unless you have any idea how remix-run/remix#6495 play with our current main and if this PR is still needed? |
I don't think we wanted to move forward with this 👍 |
Summary
This PR does two things:
loader
andaction
method defined in routes now gets automatically wrapped where we call the method before Remix, and catch any errors passing them to a logging abstraction.How to test?
Logger
instance and the demo store does not. This just verifies that it works with and without the logger passed in. If it isn't passed in, just default toconsole.error()
.