Debugging macro_rules?

I'm finding myself stymied in attempting to debug a macro that I am writing. It's just a little wrapper around println!, and I'm reminded of the depth of my lack of understanding of macro pattern matching, particularly repeating patterns. I've searched for documentation, but haven't found anything that clarifies me for instance the difference between

($($x:expr,)*)

and

($($x:expr),*)

I've seen them both in examples. The compiler error messages I'm getting are less than elucidating. Any recommendations for resources (it's not yet in the new rust book), or cogent explanations?

I'm currently hoping to refactor the following macro:

macro_rules! vvvprintln {
    () => {{ if unsafe { VERBOSITY > 2 } { println!() } }};
    ($fmt:expr) => {{ if unsafe { VERBOSITY > 2 } { println!("{}", $fmt.yellow()) } }};
    ($fmt:expr, $($arg:tt)*) => {{
        if unsafe { VERBOSITY > 2 } { println!("{}", format!($fmt, $($arg)*).yellow()) } }};
}

into something that doesn't have three separate patterns. It seems like I really ought to be able to just match against zero or more comma-delimited expressions, but am not seeing how to do it.

I found more helpful documentation at:

https://doc.rust-lang.org/reference/macros-by-example.html

and ended up successfully rewriting my macro to look more like

macro_rules! vvprintln {
    ($($arg:expr),*) => {{
        if unsafe { VERBOSITY > 1 } { println!($($arg),*) }
    }}
}

Any number of expressions followed by , token.

Any number of expressions separated by , token.

The difference is that the first one matches token streams like this:

2, 3,

And the second one does

2, 3

Note the trailing comma in the first example.

Why does $($x:expr),* match 2, 3, though? What string does the asterisk modify? Is ,* its own operator, or is there some special logic in the macro parser for commas?