-
Notifications
You must be signed in to change notification settings - Fork 9.5k
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
misc(logger): 1.3.0, package types, timeDecorate, add tests #6740
Conversation
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 this LGTM!
I think you addressed the specific list of concerns @brendankenny had about usability (stack and debugging I think it was?) but if he's back soon might as well wait for his quick look to double check.
lighthouse-logger/index.js
Outdated
@@ -1,17 +1,59 @@ | |||
/** | |||
* @license Copyright 2016 Google Inc. All Rights Reserved. |
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 what the policy is here, we haven't historically updated years because we touched the file, but it conceptually makes sense to do 2016-2018
here
@brendankenny you're our usual license police :)
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.
legally it doesnt matter one bit, so let's leave it back on 2016. seems like our rough policy is use current year when introducing new files.
lighthouse-logger/index.js
Outdated
|
||
/** | ||
* @template {Function} F | ||
* @typedef {import('./helpers').ArgumentTypes<F>} ArgumentTypes |
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.
WDYT about clarifying that file name as type-helpers
?
lighthouse-logger/index.js
Outdated
if (typeof opts.msg === 'string') { | ||
msg = opts.msg; | ||
} else if (typeof opts.msg === 'function') { | ||
// TODO turn on --strictBindCallApply when tsc is upgraded to 3.2 |
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.
🚗 🏁 #6684
lighthouse-logger/package.json
Outdated
"license": "Apache-2.0", | ||
"dependencies": { | ||
"debug": "^2.6.8", | ||
"marky": "^1.2.0" | ||
}, | ||
"devDependencies": { | ||
"@types/debug": "^0.0.31" |
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.
none of these types are exposed in our API right?
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.
correct
…ome/lighthouse into issue-6425-auto-mark-pr-logger
@hoten might have to hoist that |
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.
seems cool. :)
lighthouse-logger/index.js
Outdated
@@ -1,17 +1,59 @@ | |||
/** | |||
* @license Copyright 2016 Google Inc. All Rights Reserved. |
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.
legally it doesnt matter one bit, so let's leave it back on 2016. seems like our rough policy is use current year when introducing new files.
*/ | ||
static formatProtocol(prefix, data, level) { | ||
const columns = (!process || process.browser) ? Infinity : process.stdout.columns; | ||
const columns = isBrowser ? Infinity : (process.stdout.columns || Infinity); |
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.
nice one
lighthouse-logger/index.js
Outdated
result = originalFn.apply(this, args); | ||
} catch (err) { | ||
Log.timeEnd(status, timeEndLogLevel); | ||
err.stack = err.stack.replace(/.* at timeDecoratedFn .*\n/g, ''); |
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.
add a comment to explain this hack?
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.
ya
/** | ||
* @type {(this: *, ...args: *[]) => string} | ||
*/ | ||
const computeMsg = (_this, 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.
could this fn be as simple as:
const computeMsg = (_this, args) => {
if (typeof opts.msg === 'string') return opts.msg;
if (typeof opts.msg === 'function') return opts.msg.apply(_this, args);
throw new Error('expected msg');
};
and same with computeId()
checking that the first two cases don't return something falsy seems.. unnecessary to me, but w/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.
oh ya for sure
this only made sense before I figured out how to strongly type all this. had to verify this at runtime. but now TSC does the heavy lifting here :)
lighthouse-logger/index.js
Outdated
id = opts.id; | ||
} else if (typeof opts.id === 'function') { | ||
// TODO turn on --strictBindCallApply when tsc is upgraded to 3.2 | ||
id = opts.id.apply(this, 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.
oops! should be _this
merged master, fixed some TSC issues and added the strict |
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.
Sorry this took so long.
Lots of comments, but the approach looks good. Hopefully some helpful suggestions that can get us 90% of the benefits while simplifying things a bit.
lighthouse-core/lib/log.js
Outdated
'use strict'; | ||
|
||
/** @type {typeof import('../../lighthouse-logger')} */ | ||
module.exports = require('lighthouse-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.
is this file used anywhere?
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 will be, but I should have removed it as part of breaking up the original PR (#6515). It'll be needed so that importing the logger has types, w/o needing to use the /** @type
comment everywhere.
Will delete from this PR.
@@ -6,12 +6,54 @@ | |||
'use strict'; | |||
|
|||
const debug = require('debug'); | |||
// @ts-ignore | |||
const marky = require('marky'); |
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 you add a type file for marky
in types/marky
? It looks like it should be fairly minimal.
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.
yah?
It looks like it's basically
declare module 'marky' {
class Marky {
static mark(name: string): void;
static stop(name: string): void;
static getEntries(): PerformanceEntry[];
static clear(): void;
}
export = Marky;
}
or whatever of the many variations that would work in types/marky/index.d.ts
tsconfig.json
Outdated
@@ -6,6 +6,7 @@ | |||
"allowJs": true, | |||
"checkJs": true, | |||
"strict": true, | |||
"strictBindCallApply": true, |
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.
strict
includes strictBindCallApply
, so no need to add it 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.
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 found that this error only happens when setting
strictBindCallApply
explicitly
Maybe they fixed this in 3.2.1 or 3.2.2? I see the error with or without strictBindCallApply
, and strictBindCallApply
is indeed a Strict_Type_Checking_Options
@@ -31,9 +31,11 @@ | |||
"unit-core:ci": "jest --runInBand --coverage --ci \"lighthouse-core/\"", | |||
"unit-cli": "jest --runInBand \"lighthouse-cli/\"", | |||
"unit-cli:ci": "jest --runInBand --coverage --ci \"lighthouse-cli/\"", | |||
"unit-logger": "jest --runInBand \"lighthouse-logger/\"", | |||
"unit-logger:ci": "jest --runInBand --coverage --ci \"lighthouse-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.
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 added a second test file and it switched to the per-file report.
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 added a second test file and it switched to the per-file report.
oh. Boooo jest
*/ | ||
|
||
export type ArgumentTypes<F extends Function> = F extends (...args: infer A) => any ? A : never; | ||
export type IsFunction<T> = T extends Function ? T : never; |
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've been putting these helpers in types/externs.d.ts
(seems fine to have them there and global until(/if) we ever move our types to all be module based)
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.
move to types/externs.d.ts
? Or is the issue the lighthouse-logger
/lighthouse
split?
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.
Since the logger is still its own package it should remain within the lighthouse-logger
folder
lighthouse-logger/index.js
Outdated
const isWindows = process.platform === 'win32'; | ||
|
||
// process.browser is set when browserify'd via the `process` npm module | ||
// @ts-ignore |
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.
combine the comments? (// @ts-ignore - process.browser is set...
)
lighthouse-logger/index.js
Outdated
@@ -24,6 +66,7 @@ const colors = { | |||
}; | |||
|
|||
// whitelist non-red/yellow colors for debug() | |||
// @ts-ignore |
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.
combine comments (and maybe add something about it being non-public to explain the ts-ignore)
lighthouse-logger/index.js
Outdated
@@ -118,33 +174,149 @@ class Log { | |||
return level_ === 'verbose'; | |||
} | |||
|
|||
static time({msg, id, args = []}, level = 'log') { | |||
/** | |||
* @param {Status} param0 |
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 s/param0/status?
* @template {*[]} Args | ||
* @typedef {{ | ||
* msg: string | ((this: Class, ...args: Args) => string), | ||
* id?: string | ((this: Class, ...args: Args) => string), |
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.
what if we drop the dynamic messages and only support strings here (at least for now)? It would make this file simpler to read, simpler for type checking, and reduces the non-local reasoning needed where the timeDecorateClass
calls are located.
AFAICT, the only places we need dynamically created strings are for
- running beforePass/pass/afterPass for a gatherer, and
- running an audit
- computing a computed artifact.
Due to the structure of gatherRunner
there's no good place to decorate the gatherer functions, so #6515 only included dynamic messages for last two. We could do those with manual log.time()
/log.timeEnd()
calls (only two pairs of them needed) and be able to get rid of a lot of machinery in here.
And then at a later date we can always add support if we find we want 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.
It'll be most immediately needed by #6410, since each call to sendCommand
has a timing with an id derived from the input. As soon as this lands I'll be pushing that issue forward again. With that in mind, should this stay?
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, yeah, that's useful enough that I guess we should probably keep it
lighthouse-logger/index.js
Outdated
result = originalFn.apply(this, args); | ||
} catch (err) { | ||
Log.timeEnd(status, timeEndLogLevel); | ||
// intercept any errors and elide the time decoration from the stack trace |
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.
Sooooo...I should have been more clear in #6425 (comment). I really meant, if the stack traces are bad we should consider this. Are they? Because if not it can be really confusing why all the function calls that actually happened aren't in the trace.
Maybe we should punt on this and add it later if we find the stack traces difficult to work with? Or have you found it helpful to get clean stack traces so far?
(I'm also curious how official decorators plan on handling this)
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 didn't really come up, so I can't say I found it useful.
Monitoring tools like Rollbar do their best to clean up calls to their lib functions from stack traces. But our case is very minor, and theirs is an library meant for monitoring ...
I could go either way with this. If you think of a stack trace as primarily a tool developers use to navigate code flow, It's a nice idea to hide function calls. If you want to see exactly the whole picture, you wouldn't want things hidden. I imagine it won't ever come up again either way we go with.
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 didn't really come up, so I can't say I found it useful.
OK, cool, sounds like we should remove it then? Keeping the implementation simple(r) is always 👍
Should have resolved all of your comments now @brendankenny |
@@ -6,12 +6,54 @@ | |||
'use strict'; | |||
|
|||
const debug = require('debug'); | |||
// @ts-ignore | |||
const marky = require('marky'); |
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.
yah?
It looks like it's basically
declare module 'marky' {
class Marky {
static mark(name: string): void;
static stop(name: string): void;
static getEntries(): PerformanceEntry[];
static clear(): void;
}
export = Marky;
}
or whatever of the many variations that would work in types/marky/index.d.ts
debug.colors = [colors.cyan, colors.green, colors.blue, colors.magenta]; | ||
|
||
class Emitter extends EventEmitter { | ||
/** | ||
* Fires off all status updates. Listen with | ||
* `require('lib/log').events.addListener('status', callback)` | ||
* @param {string} title | ||
* @param {string|Error} 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.
nice
lighthouse-logger/index.js
Outdated
result = originalFn.apply(this, args); | ||
} catch (err) { | ||
Log.timeEnd(status, timeEndLogLevel); | ||
// intercept any errors and elide the time decoration from the stack trace |
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 didn't really come up, so I can't say I found it useful.
OK, cool, sounds like we should remove it then? Keeping the implementation simple(r) is always 👍
lighthouse-logger/test/index-test.js
Outdated
'use strict'; | ||
|
||
/* eslint-env jest */ | ||
/* eslint-disable no-invalid-this */ |
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.
since this is only needed for the "retains this binding" test, isolate to just in there so inadvertent lint errors will still alert
lighthouse-logger/test/index-test.js
Outdated
/* eslint-disable no-invalid-this */ | ||
|
||
const assert = require('assert'); | ||
const log = require('..'); |
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're moving to prefer the explicit require('../index.js');
to make everything a bit more explicit
lighthouse-logger/index.js
Outdated
*/ | ||
const fn = function timeDecoratedFn(...args) { | ||
const timeStartLogLevel = opts.timeStartLogLevel || 'log'; | ||
const timeEndLogLevel = opts.timeEndLogLevel || 'verbose'; |
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.
move outside fn
?
/** | ||
* @type {(this: T, ...args: Args) => R} | ||
*/ | ||
const fn = function timeDecoratedFn(...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.
drop the const fn =
, or is there a reason for it?
* Decorates (like timeDecorate) specified methods of a class. | ||
* If decorating instance methods, use the class's prototype. | ||
* If decorating static methods, use the class directly. | ||
* @template {Object|Function} Class |
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 be more specific? This is just 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.
I want to accept anything newable (classes, so basically any function) or objects (prototypes). I suppose objects and functions covers everything :P
What about something silly like this?
* If decorating instance methods, use the class's prototype. | ||
* If decorating static methods, use the class directly. | ||
* @template {Object|Function} Class | ||
* @template {keyof Class} Prop |
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 be limited to properties that are functions? E.g. you can pass a number property in (and it will accept any parameters for msg
and id
)
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 part guards against non-functions:
* @param {{[key in Prop]: TimeDecorateOpts<Class, Parameters<IsFunction<Class[key]>>>}} methods
IsFunction<T>
resolves to never if T
does not extend Function
.
opts.id = `lh:${className}:${method}`; | ||
} | ||
/** @type {IsFunction<Class[typeof method]>} */ | ||
const original = klass[method]; |
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 concerned that basically everything in this and the above function are typed as any
:)
I wonder if there's something simpler (or defining more in a d.ts
file if necessary) to get us some constraints enforced. Or we go the opposite, keeping the interface as strict as possible and going explicitly any
inside (this is partially where timeDecorate
has gone, at least with the return value).
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. My main goal was to only allow property names of functions of klass
in methods
. The current typing achieves that, but the local typing is a bit out of my reach (or TypeScript's?). But the ol' gut type checker seems right to me (AKA it looks correct).
@brendankenny wanna take a last pass? |
@brendankenny whatcha think? |
OK, so a ton of work went into this, but my vote is to not land it :/ I'm interested in what others think. Since #3745 landed, it turns out that
Adding this level of logging, however, adds non-locally-defined behavior to many of our core classes (called in a non-obvious way), isn't sound in our current type system, and adds a lot of complexity that folks who are trying to use or modify lighthouse will need to understand (it's relatively invisible until it runs into a problem, at which point there's a lot of magic to figure out). So I think the extra complexity for all of Lighthouse isn't worth it at this point. We have some |
I think it's a readability improvement and would like to see it land. That being said @brendankenny brings up some points that make me think I might misunderstand what's going on.
Is this referring to it destroying types of what it's used on, or its internal mechanics aren't sound?
Is this just that it's potentially augmenting promises and whatnot or is there something more I might be missing?
I think this is where I'm potentially underestimating. What problems might we foresee that they're even going to be aware of this functionality at all? Right now my impression is it's pretty much just going to be us.
This is a compelling argument for not starting this work, but seeing as it's done... IMO the question becomes what kind of maintenance burden does this become and how much additional work is it. @hoten maybe you have a better sense of this?
(node 12 hits LTS in April 2021) |
I think the improvements to readability are still valid. Take a look at some functions that get reduced to just one line from ~6. Then there's the functions of greater length that are bookended with timing code. I like the idea of moving timing concerns out of functionality.
I don't imagine any new functionality would be needed - the dynamic msg and id fields were the big unknown for me going into this, and that's been resolved. I believe the only possible maintenance would come from upgrading TypeScript, but we are basically Related to maintenance, how's the usage of
The latter. The typing issues don't spread globally across the codebase, just the internal mechanics. |
@brendankenny want to close this? |
I think so. If we get back in a spot where we need much more timing info we can revisit (composing generics has gotten a lot better in tsc 3.3. and 3.4 :) |
Breaking up #6515. This is part one.
Next part will roll out usages of the new
timeDecorate
functions.