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

Add debug functionality #53

Merged
merged 27 commits into from
May 28, 2021
Merged

Add debug functionality #53

merged 27 commits into from
May 28, 2021

Conversation

sbencoding
Copy link
Contributor

@sbencoding sbencoding commented Mar 25, 2020

This PR aims to implement a built-in debug feature described in #2

How it's implemented

There's a static function in the Emittery class called debugLogger, that takes in 4 parameters

  • type - type of debug log
  • debugName - set by the instance of Emittery that generated this log
  • eventName - event name related to the log
  • eventData - in case of emit, this is the data sent by it

Debugging can be triggered in 3 ways currently:

  1. the DEBUG environment variable is set to emittery or *
  2. the static isDebug function in Emittery returns true (by default this handles the environment variable, but this function can be overridden, to enable package wide debugging instead of the 3rd method.
  3. the isDebug variable is set to true on one or more instances.

Questions/suggestions

I wasn't sure if the default logger function should include some sort of timestamp.

It might add too much complexity but a verbosity option could be nice. For example currently every emit, subscribe, unsubscribe etc. event is logged, but in some cases they might not all be interesting. Of course this can be achieved by overriding the debugLogger function on Emittery, but it would be a nice convenience option.

Is it acceptable that there's one static logging function on Emittery or should the individual instanced of it be able to override this function. This would add the ability for different instances of Emittery to log differently based on their function in an application.

Fixes #2


IssueHunt Summary

Referenced issues

This pull request has been submitted to:


IssueHunt has been backed by the following sponsors. Become a sponsor

@sbencoding
Copy link
Contributor Author

Not sure why codecov doesn't pickup that I test the debugLogger static function on Emittery.
Unfortunately I'm not familiar with this software, but I tried calling the method directly from one of the test cases and it still showed that I missed that function :(

index.js Outdated
@@ -181,16 +181,37 @@ class Emittery {
};
}

constructor() {
static isDebug() {
Copy link
Owner

Choose a reason for hiding this comment

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

This should be a getter.

index.js Outdated

static debugLogger(type, debugName, eventName, eventData) {
if (typeof eventName !== 'string') {
eventName = eventName.toString();
Copy link
Owner

Choose a reason for hiding this comment

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

This is moot. Template interpolation already calls toString.

@sindresorhus
Copy link
Owner

sindresorhus commented Aug 10, 2020

Questions/suggestions

Share your opinion on these questions (with reasonings).

@sbencoding
Copy link
Contributor Author

I've implemented the requested changes, updated documentation and tests where it was needed.
My thoughts on the questions:

Timestamps

I think it would be useful only in specific scenarios if the default logging function printed out timestamps as well. The use case I can currently think of where time is relevant is an application where an emit happens periodically e.g. every 10 seconds. I think a minutes:seconds:milliseconds format would be the best in this case, since I don't expect the debug to run for hours or days.
Because I can't think of better/more use cases I don't think this would be useful to have it in by default.

Verbosity filter

I think this is useful because with multiple events and multiple instances of Emittery the logs could get cluttered very quickly if we log every subscribe/unsubscribe event too. I can think of two approaches that would be good here.

  1. Verbosity levels from 1 to 3
    Where 1 only logs emits, 2 logs emits and sub/unsub, and 3 logs everything
  2. An array of strings that defines the event types we're interested in
    For example we could add emit, subscribe and clear to it, if those are the events we're interested in. This allows for more precise control than option number 1 but also takes a bit more time to set up.

The other thing that needs to be decided here is where we implement the verbosity setting, in the Emittery class, so it affects all instances at once, or on a per instance level, so it's more precisely controllable. However on the per instance level we would have to manually update every instance if we wanted to change the setting for all instances.
Overall I would say that implementing it globally is better because it would be more efficient at reducing the overall clutter.

Static logger function

I think it would be better to have this on a per instance basis, but I guess this question is kind of the same as the implementation of the verbosity filter. The benefit of logging on a per instance basis would be that the different instances could access potentially useful data that is only available in their context. But again the drawback is that we lose the ability to update the logging function of all instances all at once.
Overall I think implementing this on a per instance basis would provide more functionality than it would take away. I couldn't come up with a use case where updating every logger function would be that important, but I can imagine that some instances would like to log information only available in their context.

Base automatically changed from master to main January 23, 2021 10:58
@sindresorhus
Copy link
Owner

Sorry, I just realized I missed the notification for this.

@sindresorhus
Copy link
Owner

Timestamps

I think timestamp can be useful for comparing how far from eachother certain events are. I agree, the date is not very useful, but it should follow the ISO9601 minus the date, so hours:minutes:seconds.milliseconds. I think timestamps should be on by default. We can add an option to remove based on future requests.

Verbosity filter

I'm not sold on this. I think it's better to just be verbose by default and we can add modes to make it less verbose when users requests it from real-world experience.

Static logger function

Yeah, it should be on the instance.


I think it should be {debug: {name: 'foo'}}, not {debugName: 'foo'}. The former lets us add more debug-related options later on.


The docs and TS docs comments needs more work regarding formatting and typos.

@sbencoding
Copy link
Contributor Author

Here are some of the changes I've made:

  • name for debugging is passed as part of an object that contains debugging setup data in the constructor
  • debugLogger is no longer static, instead logger is set in the debug options
  • isDebug that was on the instance is now set in debug options as well. The global isDebug that controls debugging for all instances is kept as it was before
  • Add timestamp to the default logger function
  • Update tests
  • Update documentation (both readme and typescript)

Let me know if I've missed anything, or there's something that could be done in a better way

P.S. finally I pass all the checks yay!

index.d.ts Outdated

To enable this feature set the DEBUG environment variable to 'emittery' or '*'. Additionally you can set the static `isDebug` variable to true on the Emittery class, or `myEmitter.debug.enabled` on an instance of it for debugging a single instance.

See API for more information on how debugging works
Copy link
Owner

Choose a reason for hiding this comment

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

All sentences should end with a .

index.d.ts Outdated

See API for more information on how debugging works
*/
type DebugLogger = (type: string, debugName: string, eventName?: EventName, eventData?: Record<string, any>) => void;
Copy link
Owner

Choose a reason for hiding this comment

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

The event data here should be strongly-typed.

const emitter = new Emittery({debug: {name: 'myEmitter'}});
emitter.on('test', data => { // do something });
//=> [16:43:20.417][emittery:subscribe][myEmitter] Event Name: test
// data: undefined
Copy link
Owner

Choose a reason for hiding this comment

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

This is too cramped. Use more empty lines to make it more readable. The example should also include the part that emits the event. Generally, code examples should be runnable on their own.

Applies to all code examples.

index.d.ts Outdated
/**
Define a name for the instance of Emittery to use when outputting debug data.

@default Empty string
Copy link
Owner

Choose a reason for hiding this comment

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

This is not a valid value for @default.

index.d.ts Outdated
// data: undefined
```
*/
name: string;
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
name: string;
readonly name: string;

index.js Outdated
@@ -9,6 +9,8 @@ const resolvedPromise = Promise.resolve();
const listenerAdded = Symbol('listenerAdded');
const listenerRemoved = Symbol('listenerRemoved');

let globalDebugFlag = false;
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
let globalDebugFlag = false;
let isGlobalDebugEnabled = false;

index.js Outdated
@@ -189,10 +191,42 @@ class Emittery {
};
}

constructor() {
static get isDebug() {
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
static get isDebug() {
static get isDebugEnabled() {

Just to make it clear.


if (!this.debug.enabled) {
this.debug.enabled = false;
}
Copy link
Owner

Choose a reason for hiding this comment

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

I don't get what the point of this is.

index.js Outdated
@@ -219,6 +257,10 @@ class Emittery {
assertEventName(eventName);
getListeners(this, eventName).delete(listener);

if (Emittery.isDebug || this.debug.enabled) {
this.debug.logger('unsubscribe', this.debug.name, eventName, undefined);
}
Copy link
Owner

Choose a reason for hiding this comment

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

This is repeated many times. There should be a utility method for this.

readme.md Outdated
Comment on lines 47 to 48
### isDebug
Enables/Disables debug mode for all instances
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
### isDebug
Enables/Disables debug mode for all instances
### isDebug
Toggle debug mode for all instances.

Formatting

@sindresorhus
Copy link
Owner

The code looks good, but the docs formatting and text needs more work.

@sbencoding
Copy link
Contributor Author

I implemented the requested changes

  1. I wasn't sure how to make DebugLogger in index.d.ts strongly-typed properly, but I've tried, please let me know if there's a better way to do this, or if how I did it is correct at all.
  2. I couldn't remove the following code
if (typeof eventName === 'symbol') {
        eventName = eventName.toString();
}

Because it throws TypeError: Cannot convert a Symbol value to a string when the string interpolation happens.
I've tried a small, isolated example:

let s = Symbol('test');
console.log(`Symbol to string: ${s}`);

This results in the same error, so I don't know if there's a way to work around this.

readme.md Outdated

###### name

Type: `string`

Default: `Empty string`
Default: `null`
Copy link
Owner

Choose a reason for hiding this comment

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

I prefer using undefined over null.

emitter.on('test', data => { // do something });
emitter.emit('test');
Copy link
Owner

Choose a reason for hiding this comment

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

Readme and index.d.ts should be in sync.

@sindresorhus
Copy link
Owner

I wasn't sure how to make DebugLogger in index.d.ts strongly-typed properly, but I've tried, please let me know if there's a better way to do this, or if how I did it is correct at all.

I would recommend writing some type tests to confirm it's correct: https://github.com/sindresorhus/emittery/blob/main/index.test-d.ts

I couldn't remove the following code

Ah yeah. I forgot it's not interpolatable.

@sbencoding
Copy link
Contributor Author

I've implemented the changes, and wrote some type tests for the strongly-typed logger

@sindresorhus sindresorhus changed the title Implements debug feature as described in #2 Add debug functionality May 28, 2021
@sindresorhus sindresorhus merged commit 43b6deb into sindresorhus:main May 28, 2021
@sindresorhus
Copy link
Owner

Looks good now. Thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Built-in debug info
2 participants