You can't use expvar.Func
to expose a bunch of expvar types
Suppose, hypothetically, that you have a collection of statistics
that are each one of the expvar
package's types. You want to put them in
a namespace (so they are all 'mything.var1
', 'mything.var2
' and
so on), but you'd like to avoid the tedious clutter
of registering each expvar variable by hand with .Set()
. So you
have a clever idea.
First, you will embed all of the variables in a structure:
var somestats struct { var1, var2, var3 expvar.Int var4, var5, var6 expvar.Int }
Then you will write a Stats()
function that simply hands back
the structure and then register it through expvar.Func()
:
func ReportStats() interface{} { return somestats }
Unfortunately, this will not work (at least today). As I mentioned
in my first set of notes on the expvar package,
expvar.Func
turns what your function returns into JSON by using
json.Marshal, and
this only returns exported fields. None of the expvar variable
types have any exported fields, and so as a result expvar.Func()
will convert them all to empty JSON (or possibly malfunction).
You just can't get there from here.
This is kind of a bug (at a minimum, expvar.Func
should document
this restriction), but it's unlikely to change (apart from the
documentation being updated). Beyond it not working today, there's
no way to have a simple ReportStats
function like this that work
safely, and since you can't do this there's little to no point in
making expvar variable types JSON-serializable through json.Marshal.
(To make this work, each expvar type would implement a MarshalJSON()
method that did the appropriate thing. In fact, since expvar.String
is really MarshalJSON() in disguise, you could just make one call the
other.)
Sidebar: Why clear and complete documentation matters
Here is a question: is it deliberate that the 'thing to JSON'
function for expvar.Var
is not called MarshalJSON
, or is it a
historical accident? You can certainly argue that because my pattern
above is fundamentally wrong, it's a feature that it doesn't work
at all. Thus the choice of not using MarshalJSON
in the expvar.Var
interface could be entirely deliberate and informed, since it makes
a broken thing (and all its variants) simply not work.
Or this could be ultimately a mistake on the order of using
String()
in the expvar.Var
interface, and
so something that would be corrected in a hypothetical Go 2 (which
is allowed to change APIs).
Without better documentation, people who come along to Go later just don't know. If you want to preserve the intent of the original designers of the language and the standard library, it really helps to be clear on what that intent is and perhaps the logic behind it. Otherwise it's hard to tell deliberate decisions from accidents.
|
|