Skip to content
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

Merged
merged 37 commits into from Dec 20, 2021
Merged

Conversation

Josh-Cena
Copy link
Collaborator

@Josh-Cena Josh-Cena commented Nov 23, 2021

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:

  • All fields of picocolors. This includes yellow, createColors, isColorSupported, etc.
  • Formatters. These functions have the same signature as the formatters of 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.
  • The interpolate function. It is a template literal tag.
  • Logging functions. All logging functions can both be used as functions (in which it has the same usage as 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:

logger.info`Hello %i${name}! You have %n${money} dollars. Here are the ${
  items.length > 1 ? 'items' : 'item'
} on the shelf: ${items}
To buy anything, enter %c${'buy x'} where %c${'x'} is the item's name; to quit, press %c${'Ctrl + C'}.`;

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]

@Josh-Cena Josh-Cena added the pr: polish This PR adds a very minor behavior improvement that users will enjoy. label Nov 23, 2021
@facebook-github-bot facebook-github-bot added the CLA Signed Signed Facebook CLA label Nov 23, 2021
@netlify
Copy link

netlify bot commented Nov 23, 2021

✔️ [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

@github-actions
Copy link

github-actions bot commented Nov 23, 2021

⚡️ Lighthouse report for the changes in this PR:

Category Score
🟠 Performance 80
🟢 Accessibility 98
🟢 Best practices 100
🟢 SEO 100
🟢 PWA 95

Lighthouse ran on https://deploy-preview-5994--docusaurus-2.netlify.app/

@github-actions
Copy link

github-actions bot commented Nov 23, 2021

Size Change: +450 B (0%)

Total Size: 652 kB

Filename Size Change
website/build/assets/css/styles.********.css 101 kB +206 B (0%)
website/build/index.html 29.6 kB +215 B (+1%)
ℹ️ View Unchanged
Filename Size Change
website/.docusaurus/globalData.json 37.8 kB 0 B
website/build/assets/js/main.********.js 484 kB +29 B (0%)

compressed-size-action

@slorber
Copy link
Collaborator

slorber commented Nov 23, 2021

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:

@armano2
Copy link
Contributor

armano2 commented Nov 23, 2021

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

@Josh-Cena
Copy link
Collaborator Author

However I'd like to abstract the underlying lib under our own abstraction and how logging levels for core, lifecycles and each plugins.

Agree. So we create things like pathColor(), iddentifierColor(), infoColor() methods and import them instead of importing chalk, so that it doesn't matter what lib we use? I've thought about that

@slorber
Copy link
Collaborator

slorber commented Nov 24, 2021

So we create things like pathColor(), iddentifierColor(), infoColor() methods

Not sure what you mean here.

I like the idea of having regular error levels like trace/info/warn/error (like the remotion example)
Do we need more coloring features outside of these use cases?

@Josh-Cena
Copy link
Collaborator Author

Do we need more coloring features outside of these use cases?

We use color-coding a lot. For example, the swizzling message uses almost all colors, and one success message includes green, cyan, and blue. These colors are also used quite inconsistently—although I don't think anyone would complain about cyan being used for a code identifier (Layout) in one place and a path (src/css/custom.css) in another, my OCD presses me to make a spec about color coding.

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.

@Josh-Cena Josh-Cena added the status: don't merge yet This PR is completed, but we are still waiting for other things to be ready. label Nov 24, 2021
@slorber
Copy link
Collaborator

slorber commented Nov 24, 2021

👍 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

@Josh-Cena
Copy link
Collaborator Author

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 @docusaurus/utils/consoleUtils. But anyways, we'll figure this out. I definitely think we should migrate to picocolors though, it's not just me who has a natural aversion to prototype modification (chalk.red.bold()). It's a pity that the fleet of Sindre Sorhus packages will always depend on chalk.

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.

@Josh-Cena Josh-Cena changed the title refactor: migrate from chalk to picocolors refactor: unify log color-coding and formatting with new logger utility Nov 28, 2021
@Josh-Cena Josh-Cena marked this pull request as draft November 28, 2021 02:07
@Josh-Cena Josh-Cena removed the status: don't merge yet This PR is completed, but we are still waiting for other things to be ready. label Nov 28, 2021
@Josh-Cena
Copy link
Collaborator Author

image

image

People can try it out on their devices. Here are some demos.

Copy link
Collaborator

@slorber slorber left a 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}.`;
Copy link
Collaborator

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"
Copy link
Collaborator

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

Copy link
Contributor

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

Copy link
Collaborator Author

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?

Copy link
Collaborator

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

packages/docusaurus-theme-translations/package.json Outdated Show resolved Hide resolved
packages/docusaurus-utils-validation/package.json Outdated Show resolved Hide resolved
@@ -238,6 +239,7 @@ function isInternalCommand(command) {

async function run() {
if (!isInternalCommand(process.argv.slice(2)[0])) {
// @ts-expect-error: Hmmm
Copy link
Collaborator

Choose a reason for hiding this comment

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

?

Copy link
Collaborator Author

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;
Copy link
Collaborator

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

Copy link
Collaborator Author

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.

Copy link
Collaborator

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,
Copy link
Collaborator

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

Copy link
Collaborator Author

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

Copy link
Collaborator

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

packages/docusaurus-logger/src/index.ts Show resolved Hide resolved
@Josh-Cena
Copy link
Collaborator Author

Josh-Cena commented Dec 1, 2021

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.

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.

Do we really need template literals and interpolation?

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 routes.map((route) => logger.path(route)).join('\n') every time, and also parse the string much more easily (compared to using regex to match every tag as in Chalk's {red {bold hey} josh}. The new templating approach they use is also tagged templates: https://github.com/chalk/chalk-template

I personally designed this API from the perspective of a Docusaurus developer. I've written many log messages scattered with things like ${chalk.cyan(path)} which I wish can be simplified. Also, this syntax prevents prettier from splitting the function call onto multiple links, which improves with readability actually. Considering logger.error(`Error while executing ${logger.code("docusaurus build")}`), I'm willing to change the template to logger.error`Error while executing %code${'docusaurus build'}` if that looks better?

@slorber
Copy link
Collaborator

slorber commented Dec 3, 2021

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.

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

@Josh-Cena Josh-Cena added the status: awaiting review This PR is ready for review, will be merged after maintainers' approval label Dec 19, 2021
Copy link
Collaborator

@slorber slorber left a 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

@slorber slorber merged commit 770418f into main Dec 20, 2021
@slorber slorber deleted the jc/picocolors branch December 20, 2021 16:25
@Josh-Cena Josh-Cena removed the status: awaiting review This PR is ready for review, will be merged after maintainers' approval label Dec 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed Signed Facebook CLA pr: polish This PR adds a very minor behavior improvement that users will enjoy.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants