-
-
Notifications
You must be signed in to change notification settings - Fork 8.9k
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
refactor: unify log format with new logger utility #5994
Conversation
✔️ [V2] 🔨 Explore the source changes: ac3f1af 🔍 Inspect the deploy log: https://app.netlify.com/sites/docusaurus-2/deploys/61c0a9d10e00f3000806dc65 😎 Browse the preview: https://deploy-preview-5994--docusaurus-2.netlify.app |
⚡️ Lighthouse report for the changes in this PR:
Lighthouse ran on https://deploy-preview-5994--docusaurus-2.netlify.app/ |
Size Change: +450 B (0%) Total Size: 652 kB
ℹ️ View Unchanged
|
Honestly not really willing to do this change You'll find a lot of context why tthere: babel/babel#13783 (comment) It's not really worth it to change our logging lib every time there's a new one in town However I'd like to abstract the underlying lib under our own abstraction and how logging levels for core, lifecycles and each plugins. See some inspiration:
|
100% agree with @slorber, there is no reason to actually do swap, creating abstract for logging levels is a great idea but colors is just.... i feel like laughing |
Agree. So we create things like |
Not sure what you mean here. I like the idea of having regular error levels like trace/info/warn/error (like the remotion example) |
We use color-coding a lot. For example, the swizzling message uses almost all colors, and one success message includes My idea is that each message can be classified as trace/info/warn/error as you mentioned, but also that within a message we may want to color-code certain entities, e.g. file paths, code identifiers. These can all be encapsulated and exposed as a declarative API so we can easily swap out the implementation. |
👍 agree we should normalize these extra use-cases. I don't have a clear picture of all those use-cases though we can add them into a single place and iterate from there. I'd like to have a docusaurus-logging package and only this one would depend on chalk, allowing to change the logging lib more seamlessly |
Whoa, that's more than I wished... I'm more thinking about I'll not argue on this point though; I've observed many, many OSSes with multiple maintainers arguing over chalk vs. pico/nanocolors when I was doing the field research🤷♂️ The only ones that did the migration smoothly are those with a dictating maintainer. |
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 result looks good but I'm not sold on 2 things:
-
This PR is about creating the logging interface, not switching the underlying lib. We want it to be initially simple enough so that eventually switching lib later becomes easy, and could be done with a feature flag to remove the risk of that switch.
-
Do we really need template literals and interpolation?
), | ||
); | ||
logger.error('Minimum Node.js version not met :('); | ||
logger.info`You are using Node.js %n${process.version}, Requirement: Node.js %n${requiredVersion}.`; |
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.
even if it works, I don't really see the value of introducing a fancy logging syntax using template literals
never seen this anywhere before
}, | ||
"license": "MIT", | ||
"dependencies": { | ||
"picocolors": "^1.0.0" |
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 rather keep chalk really, changing this dependency that works has little value (unless you can measure a significant the perf impact?) and only introduce useless risk
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 do agree with you, changing packages when current one is maintained its bad choice
chalk is not even removed from dependencies, as its used by underlying libs anyway
as for micro benchmarks, adding this interpolation is slower than adding 500 colors to message manually
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.
It's rather about neither performance or bundle size, because as we realized, we neither get rid of Chalk nor save much perf with the template interpolation. But just as I said, picocolors is shinier because: (a) it doesn't pollute the prototype chain (I have such an aversion to Chalk's chalk.red.bold()
magic) (b) it has a much smaller, cleaner API surface which allows us to re-export everything in an intuitive way (...pico
) which just includes the functions we know we will need.
I'm fine with going back to Chalk. But as we are making a new package anyways, why don't we also choose a new lib that many big OSSes are already adopting?
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 goal of this PR is not to change behavior and implementation, it's just to refactor to another logging architecture with less coupling leaking inside our codebase.
Please let's focus on what we decided to do: provide a logging abstraction that encapsulate the needs we have.
it has a much smaller, cleaner API surface which allows us to re-export everything in an intuitive way
the goal is 100% do actually not do that, that's the motivation behind this abstraction: not making the underlying leak into our codebase 😅
I'm fine with going back to Chalk. But as we are making a new package anyways, why don't we also choose a new lib that many big OSSes are already adopting?
That can be decided in a separate PR later, but be ready to have a lot of pushback from my side
In the meantime I prefer that we separate architecture/refactor changes from implementation details changes, and have many smaller PRs easier to review and agree on, compared to a larger PR that we disagree on many details
@@ -238,6 +239,7 @@ function isInternalCommand(command) { | |||
|
|||
async function run() { | |||
if (!isInternalCommand(process.argv.slice(2)[0])) { | |||
// @ts-expect-error: Hmmm |
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.
?
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 enabling ts-check
, but there's a type incompatibility. I don't have the bandwidth to investigate why that's the case for now, but I'll probably fix it before merge
success, | ||
}; | ||
|
||
export default 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.
Considering we'll want to provide configurable logging levels later, maybe exposing a static logger instance like this is not ideal? Not sure yet what our final API will look like though, we'll probably need more reactors later anyway
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.
logger
behaves like console
in every way. I plan to make it read env vars when we support logging levels.
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.
Later I'd like all loggers to have a name, they should rather be created and added to some context, eventually log their name to the console. We'd call something like createLogger()
Config could look like this:
const siteConfig = {
logging: {
core: "warn"
browser: "error",
plugins: {
"docusaurus/plugin-content-docs": "info",
"docusaurus/plugin-content-blog": "info",
}
}
}
We can see how to implement that later though
} | ||
|
||
const logger = { | ||
...pico, |
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 point of this abstraction is actually to not expose the underlying lib to our own code, so pico/chakl should not be exposed
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.
We occasionally need to use the imperative APIs (red
, bold
) as opposed to the semantic declarative ones (path
, id
). So the alternative to re-exposing pico
is exporting every color, I'm fine with that as well
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.
So the alternative to re-exposing pico is exporting every color, I'm fine with that as well
We should only expose colors that we actually use, it's like a "terminal design system / design tokens".
We really don't want to expose pico/chalk/whatever in any way here
Picocolors and Chalk are exactly the same. I'm fine with moving it back to Chalk, but I'm unsure of why we don't want to move. This has absolutely no effect on the end user.
The alternative of... logger.warn`The following routes don't exist: %p${routes}` ...is this: logger.warn(`The following routes don't exist:
- ${routes.map((route) => logger.path(route)).join('\n')}); You didn't see it nowhere. Chalk has templates which they removed in v5 because of the bundle size, but we can build our own version of it, tailored to our needs. Template tags allow us to transform arrays into the "list" form without writing out I personally designed this API from the perspective of a Docusaurus developer. I've written many log messages scattered with things like |
If it's the same, then there's no reason to change. If it's not the same, there's risk we'll take for no real benefits. And this is not the initial goal of this PR About the interpolation API, the best API is no API. What you design here will become public API surface, as users will access their site plugins logger in site config. I don't want to introduce a premature public API surface, nor to remove your interpolation API once this becomes exposed. Let's do the simple thing first: just use regular JS logging features + some colors. No shorthands, no magic behavior for now |
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.
LGTM, let's move on and merge this for now
Motivation
Continuing the spirit of #4579. We have seen a lot of libs migrating to picocolors because it's attractive in almost every way: small, performant, clean code... I see no reason to continue using chalk.
To prevent anyone from arguing over the ROI of this switch, we decided to encapsulate the coloring logic in a new package
@docusaurus/logger
. This package will be the only one doing the logging work. Read the README for that package:@docusaurus/logger
An encapsulated logger for semantically formatting console messages.
APIs
It exports a single object as default export:
logger
.logger
has the following properties:picocolors
. This includesyellow
,createColors
,isColorSupported
, etc.picocolors
. Note that their implementations are not guaranteed. You should only care about their semantics.path
: formats a file path or URL.id
: formats an identifier.code
: formats a code snippet.subdue
: subdues the text.num
: formats a number.interpolate
function. It is a template literal tag.console.log
) or template literal tags.info
: prints information.warn
: prints a warning that should be payed attention to.error
: prints an error (not necessarily halting the program) that signals significant problems.success
: prints a success message.Using the template literal tag
The template literal tag evaluates the template and expressions embedded.
interpolate
returns a new string, while other logging functions prints it. Below is a typical usage:An embedded expression is optionally preceded by a flag in the form
%[a-z]+
(a percentage sign followed by a few lowercase letters). If it's not preceded by any flag, it's printed out as-is. Otherwise, it's formatted with one of the formatters:%p
:path
%i
:id
%c
:code
%s
:subdue
%n
:num
If the expression is an array, it's formatted by
`\n- ${array.join('\n- ')}\n`
(note it automatically gets a leading line end). Each member is formatted by itself and the bullet is not formatted. So you would see the above message printed as:[OMITTED]