Motivation

You’ve written a great Apps Script library and you want to know how many people are using it, and perhaps even which parts of the library they are using, and how often. Perhaps you have a new version out, and need to know whether anybody is still using the old version. You may also have a set of libraries and you want to know whether users have flipped to the new version. All these things are important to know, but impossible to find out using the Apps script platform as is. This series of posts will show how to track all that using 2 methods – standalone or together.

  • The properties services built in to Apps Script
  • The measurement protocol in Analytics

First we’ll take a look at using the properties service.  The Analytics option is a little more complex to set up, and we’ll get to that in later posts.

How can we use the Properties Service to track usage.

Each script has its own unique set of property stores. Specifically, we’ll be using the script property store to store aggregated usage, and the user property store to track usage by unique users. All this can work because although a Script has its own property stores, any libraries it includes also have their own set of stores – so we can track aggregated views of library usage irrespective of which script is actually calling them.

How does it work

You include the bmLibraryTracking library in any libraries you want to track, and pass over the property stores of the library you want to track, along with any optional metadata you want to store. .

In all these examples, I’ll be using a library called bmPreFiddler, which has tracking enabled as below.  The simplest way to enable tracking is to create a script in your library with the content below,  amending the arguments to match your library’s name and version

You don’t need to call anything – it will automatically log a visit every time a script that uses the library runs.

// tracking usage of library snippet
// just enter the name of the library and the version in the arguments
var Trackmyself = ((options) => {
const track = bmLibraryTracking.Track

const trackingOptions = {
...options,
userStore: PropertiesService.getUserProperties(),
scriptStore: PropertiesService.getScriptProperties()
}
track.stamp(trackingOptions)

// so we can get reports
return {
exportUsage: (options = {}) => track.scriptReport({ ...trackingOptions, ...options }),
currentUserUsage: (options = {}) => track.userReport({ ...trackingOptions, ...options })
}

})({
name: 'bmPreFiddler',
version: '7'
})
Tracking code in library

That’s all that’s needed to track library usage.

What does it track

Each unique user is assigned an opaque id. The tracker is able to detect whether a user is new or repeat by using the libraries userProperty store. In other words, Apps Script is able to identify users from each other, but the tracking library doesn’t specifically need to know who exactly they are. This avoids keeping any personally identifable data about users, yet is able to disambiguate them. Here is a typical user visit entry. It keeps track of all the versions of a library a given user has used and the first and last time they used them.

{ userId: 'egko1f6ht29aj',
visits: 3,
firstVisit: 1621951063378,
name: 'bmPreFiddler',
userKey: 'ptrack_bmPreFiddler',
versions:
[ { versionId: '7',
versionFirstVisit: 1621951063378,
versionVisits: 2,
versionLastVisit: 1621951247786 },
{ versionId: '5',
versionFirsVisit: 1621951063742,
versionVisits: 1,
versionLastVisit: 1621951063742 } ],
version: '7',
lastVisit: 1621951247786,
trackerVersion: 'v1.2',
scriptId: 'cohw1f6ht29bk' }
User visit data

In addition to user specific tracking each visit updates an aggregate view of library usage.

{ scriptId: 'cohw1f6ht29bk',
firstVisit: 1621951063378,
visits: 3,
uniqueUsers: 1,
versions:
[ { versionId: '7',
versionFirstVisit: 1621951063378,
versionVisits: 2,
versionLastVisit: 1621951247786 },
{ versionId: '5',
versionFirstVisit: 1621951063742,
versionVisits: 1,
versionLastVisit: 1621951063742 } ],
name: 'bmPreFiddler',
scriptKey: 'ptrack_bmPreFiddler',
lastVisit: 1621951247786,
trackerVersion: 'v1.2' }
Aggregated library usage

Reviewing the results

Both the currently active user and the script aggregate tracked data can be viewed from within the library. If you’ve used the suggested snippet above, you can just do this.

  console.log(Trackmyself.exportUsage())
console.log(Trackmyself.currentUserUsage())
Viewing tracking data inside library

You could use this, for example, to change library behavior on a user’s first visit.

Get the results from another script

If you want to review the results outside the library for reporting, since the reporting is exposed as above, you just need to do this

console.log(bmPreFiddler.Trackmyself.exportUsage())
console.log(bmPreFiddler.Trackmyself.currentUserUsage())
exporting library usage

You can then access this from another script (perhaps one that summarizes multiple libraries), by including a reference to that method in your library. Note that user level tracking only really makes sense from within the library itself, other than for testing, so if you are reporting on usage externally you’ll probably only want to use the exportUsage() method

Here’s an example of a script you might want to get the usage stats of a number of libraries

function myFunction() {

console.log(bmPreFiddler.Trackmyself.exportUsage())
console.log(bmLibraryTracking.Trackmyself.exportUsage())
console.log(bmFiddler.Trackmyself.exportUsage())

}
reporting on multiple libraries

Deeper tracking

So far we’ve looked at basic tracking – knowing that 100 different people have made 2000 visits to 6 different versions of your library is useful to know. But let’s say we want to find if there’s any especially popular or unused function in a library. We can use tracking for that too.

We can just make up a name and add it to the function to be tracked. Note that each time it’s called will increment the visit count (so best not to do this in a loop)

    const trackingOptions = {
name: 'bmPreFiddler-getSheet',
version: '5',
userStore: PropertiesService.getUserProperties(),
scriptStore: PropertiesService.getScriptProperties()
}
bmLibraryTracking.Track.stamp(trackingOptions)
tracking individual functions or code snippets

And of course it can be reported on using exactly the same techniques are previously mentioned

const trackreportGetSheet = () => {
const track = bmLibraryTracking.Track
const trackingOptions = {
name: 'bmPreFiddler-getSheet',
userStore: PropertiesService.getUserProperties(),
scriptStore: PropertiesService.getScriptProperties()
}
console.log(track.userReport(trackingOptions))
console.log(track.scriptReport(trackingOptions))
}
reporting on tracked methods

You could also conditionally track to see if a particular condition is ever true, track errors or unusual code paths or whatever – all using the same technique.

Metadata

You can add your own small piece of arbitrary meta data to the tracking also. There are 2 types of user meta data

  • User meta data – this would typically be set the first time a user is seen if you want to record something for future reference.
  • Visit meta data – this could be set each time a user is detected. It might be useful for recording something about what each specific user did last time they visited the library

Here’s an example of writing both user meta data and visit meta data. This confected idea is if the same user is accessing the same sheet id as he did the last time we can take some special action.

  console.log(bmPreFiddler.Trackmyself.exportUsage({name: "bmPreFiddler-getSheet"}))
console.log(bmPreFiddler.Trackmyself.currentUserUsage({name: "bmPreFiddler-getSheet"}))
meta data uses

Here’s what meta data looks like

{ userId: 'hdli1f6i4v2h0',
visits: 1,
firstVisit: 1621959346715,
name: 'bmPreFiddler-getSheet',
userKey: 'ptrack_bmPreFiddler-getSheet',
versions:
[ { versionId: '5',
versionFirstVisit: 1621959346715,
versionVisits: 1,
versionLastVisit: 1621959346715 } ],
meta: { comment: 'I dont know if anybody uses this function much' },
version: '5',
lastVisit: 1621959346715,
trackerVersion: 'v1.2',
visitMeta:
{ comment: 'is this the same as last time',
id: '1DlKpVVYCrCPNfRbGsz6N_K3oPTgdC9gQIKi0aNb42uI' },
scriptId: 'lhlw1f6i4v2ik' }
Meta data report

Library clusters

You can share Apps Script library tracking data across multiple libraries simply by introducing a shared library that each of your libraries include. You then pass the Properties service of the shared library (which you would need to expose from the shared library) to the tracking library and each library would be able to disambiguate users.

This is my preferred method as I only need to attach to a single library to analyze all my data for all libraries, rather than add tracking reporting for each library individually. Here’s how to set it up.

  • Create a script that will be your centralized repository – yourLibraryReports, and create a Trackmyself file. Note that I’ve added a deleteAllProperties method. This is just so I can start again by deleting all tracking data for all contributing libraries during testing
// tracking usage of library snippet
var Trackmyself = ((options) => {
const track = bmLibraryTracking.Track

const trackingOptions = {
...options,
userStore: PropertiesService.getUserProperties(),
scriptStore: PropertiesService.getScriptProperties()
}

// optionally record usage of this script
// this could generate a bunch of traffic you are not interested in
// if you have a lof of libraries aggregating
// however it will allow you to track unique users across all your libraries
// track.stamp(trackingOptions)

// so we can get reports to report on other scripts
return {
exportUsage: (options = {}) => track.scriptReport({...trackingOptions,...options}),
currentUserUsage: (options = {}) => track.userReport({...trackingOptions, ...options}),
getAllVisits: () => track.getAllVisits(trackingOptions),
// this can be used to centralize trackin of other libraries
stamp: (options = {}) => track.stamp({...trackingOptions, ...options}),
// danger: just for testing only - clear out all stores
deleteAllProperties: () => {
trackingOptions.userStore.deleteAllProperties()
trackingOptions.scriptStore.deleteAllProperties()
}
}

})({
name: 'bmLibraryReporter',
version: '8',
failSilently: false
})
bmLibraryReports
  • Include the bmTrackingLibrary
  • For every library you want to track, modify its Trackmyself file slightly so that instead of using its own property store, it uses the propertystore of a centralized tracking store and include a library reference to yourLibraryReports. You don’t need the bmLibraryTracking library anymore.
// tracking usage of library snippet to a centralized store
var Trackmyself = ((trackingOptions) => {
const track = bmLibraryReporter.Trackmyself

// this will record usage in central library store
track.stamp(trackingOptions)

// so we can get reports
return {
exportUsage: (options = {}) => track.scriptReport({...trackingOptions,...options}),
currentUserUsage: (options = {}) => track.userReport({...trackingOptions,...options})
}

})({
name: 'bmFiddler',
version: '12',
failSilently: false
})
add this to every library you want to track centrally

Reporting on library clusters

To access the centrally shared library tracking, add a reference to yourLibraryReports, and you can get the script usage data like this for any contributing library

const reports = () => {
// this will report on the usage of your library reporter aggregator script
console.log(bmLibraryReporter.Trackmyself.exportUsage())

// this will report on a spcific contributor library
console.log(bmLibraryReporter.Trackmyself.exportUsage({name: 'bmFiddler'}))

// this will show your own personal usage of a given library
console.log(bmLibraryReporter.Trackmyself.currentUserUsage({name: 'bmFiddler'}))
console.log(bmLibraryReporter.Trackmyself.currentUserUsage({name: 'bmPreFiddler'}))
}
reporting on library clusters

Creating charts of usages

To take it a little further, you may want to do some consolidated reporting, and perhaps do some charting. This should get you started.

  • Enhance yourLibraryReports to export an extra couple of methods. Specifically we want the method that gets all scriptUsage that’s being tracked
// tracking usage of library snippet
var Trackmyself = ((options) => {
const track = bmLibraryTracking.Track

const trackingOptions = {
...options,
userStore: PropertiesService.getUserProperties(),
scriptStore: PropertiesService.getScriptProperties()
}

// optionally record usage of this script
// this could generate a bunch of traffic you are not interested in
// if you have a lot of libraries aggregating
// however it will allow you to track unique users across all your libraries
// track.stamp(trackingOptions)

// so we can get reports to report on other scripts
return {
exportUsage: (options = {}) => track.scriptReport({...trackingOptions,...options}),
currentUserUsage: (options = {}) => track.userReport({...trackingOptions, ...options}),
getAllVisits: () => track.getAllVisits(trackingOptions),
// this can be used to centralize trackin of other libraries
stamp: (options = {}) => track.stamp({...trackingOptions, ...options}),
// danger: just for testing only - clear out all stores
deleteAllProperties: () => {
trackingOptions.userStore.deleteAllProperties()
trackingOptions.scriptStore.deleteAllProperties()
},
getAllScriptUsage: ()=> track.getAllScriptUsage(trackingOptions),
getVisitorReport: ()=>track.getVisitorReport(trackingOptions)
}

})({
name: 'bmLibraryReporter',
version: '8',
failSilently: false
})
Add method to get all script usage
  • Create a spreadsheet and add a container bound script to get the reporting data. To simplify I’m using my bmPreFiddler library – for more details see  Handly helper for fiddler
  • Add a reference to bmPreFiddler and to yourLibraryReports
  • This code will create a sheet with 1 row per line for version/library combination

const makeCharts = () => {

// this will get all scripts being tracked from my shared reporting store
const visits = bmLibraryReporter.Trackmyself.getAllScriptUsage()
console.log(visits)

// now to add all that data to a sheet in the currently attached spreadcsheet
const fiddler = new bmPreFiddler.PreFiddler().getFiddler({
sheetName: 'library-reporter',
createIfMissing: true
})

// blow out to a line per version and sort
fiddler.setData(visits.reduce((p, c) =>
p.concat(c.versions.map(v => ({ ...c, ...v, versions: c.versions.length, nameVersion: c.name '-' v.versionId })))
, [])).sort('nameVersion')

// now dump out to sheet
fiddler.dumpValues()

}
making a sheet from usage data
  • The data will look like this
  • Make a chart – this one simply reports on name/version visits

Keeping track of updates

Libraries are pretty hard to keep track of in Apps Script. If you update one, you have to find all the libraries that are dependent on it, then follow through and update all of them, remembering to redeploy everything – it’s a maze. If you use github for your projects, scrviz can help find everything you need to do.

  • find your library repo in the the scrviz library view
  • You’ll see all the projects that are dependent on it

For more on scrviz, see Every Google Apps Script project on Github visualized

What’s next

In the next post on this subject, I’ll show you how to record all this stuff using Google Analytics.

Links

bmLibraryTracking – 1GIpX-loeYv2swfT2mcYCUvduAXtoYdzenzIYXt4M_1YLmlN7eMrO1h_P

github – https://github.com/brucemcpherson/bmLibraryTracking

scrviz – https://scrviz.web.app/?repo=brucemcpherson%2FbmLibraryTracking

bmPreFiddler – 13JUFGY18RHfjjuKmIRRfvmGlCYrEkEtN6uUm-iLUcxOUFRJD-WBX-tkR

github – https://github.com/brucemcpherson/bmPreFiddler

scrviz  – https://scrviz.web.app/?repo=brucemcpherson%2FbmPreFiddler