# Privatus Analytics for Perl
Adds the cookieless [Privatus Analytics](https://privatusanalytics.com)
tracking script to your Perl web app. There is a plugin for Mojolicious, a
plugin for Dancer2, a Plack middleware for any PSGI app, and a plain module
you can use anywhere.
Privatus Analytics counts visits, sources, pages and conversions without
cookies, local storage or fingerprinting, so analytics does not need a consent
banner by itself. Check with your own counsel for your country.
This distribution only prints the browser script and a few HTML attributes. It
never sends events from your server and sets no cookies. You need a Privatus
Analytics account to see reports, and there is a free plan. Sign up at
https://privatusanalytics.com.
## Requirements
* Perl 5.20 or later.
* Optional: Mojolicious 9 or later, Dancer2 0.400000 or later, or Plack.
* A Privatus Analytics site id (it starts with `pa_`). You find it under
Site settings, Tracking.
## Install
```sh
cpanm PrivatusAnalytics
```
For the full setup guide and every tracker option, see the [Privatus Analytics
docs](https://privatusanalytics.com/docs).
Set your site id with an environment variable:
```sh
PRIVATUS_SITE_ID=pa_YOURSITEID
```
or pass it as the `site_id` setting, as shown below.
## Add the tag
### Mojolicious
```perl
# Mojolicious::Lite
plugin PrivatusAnalytics => { site_id => 'pa_YOURSITEID' };
# Full app, in startup
$self->plugin(PrivatusAnalytics => { site_id => 'pa_YOURSITEID' });
```
Settings in your app config under `privatus_analytics` work too. Put the helper
in the `
` of your layout:
```
My app
%= privatus_analytics_tag
```
### Dancer2
```yaml
# config.yml
plugins:
PrivatusAnalytics:
site_id: pa_YOURSITEID
```
```perl
package MyApp;
use Dancer2;
use Dancer2::Plugin::PrivatusAnalytics;
```
In a Template Toolkit layout:
```
My app
[% privatus_analytics_tag() %]
```
### Plack (any PSGI app)
```perl
# app.psgi
use Plack::Builder;
builder {
enable 'PrivatusAnalytics', site_id => 'pa_YOURSITEID';
$app;
};
```
The middleware adds the tag for you (see auto inject below).
### Plain Perl
```perl
use PrivatusAnalytics;
my $privatus = PrivatusAnalytics->new(site_id => 'pa_YOURSITEID', environment => 'production');
print $privatus->tag;
```
### What it prints
In production it prints:
```html
```
The tag prints nothing when there is no site id, and nothing outside
production (see below). The one exception is the queue stub: when it is on,
it still prints (see [Track from your own JavaScript](#track-from-your-own-javascript)). If your app uses a Content Security Policy nonce (a
random code that lets a script run), pass it as the `nonce` setting. The
Mojolicious plugin also uses the `csp_nonce` stash value, the Dancer2 plugin
the `csp_nonce` var, and the Plack middleware `$env->{'plack.csp_nonce'}`.
### Or let it add the tag for you
If you would rather not touch your layouts, switch on auto inject:
```perl
plugin PrivatusAnalytics => { site_id => 'pa_YOURSITEID', auto_inject => 1 };
```
```yaml
plugins:
PrivatusAnalytics:
site_id: pa_YOURSITEID
auto_inject: 1
```
In Mojolicious it uses an `after_render` hook, and in Dancer2 an `after` hook.
The Plack middleware always does it (set `auto_inject => 0` to stop it).
It adds the tag just before `` in uncompressed `text/html` responses
with status 200. It skips Ajax requests, pages without a ``, and pages
that already have the tag. Responses that carry a `Content-Encoding` header
are left alone, and so are streamed Plack responses. With Plack, enable the
middleware after `Plack::Middleware::Deflater` (so it runs inside it), or use
the helper instead. The `Content-Length` header is fixed for you.
## Configuration
Every setting is optional except the site id. Each plugin takes the same
settings.
```perl
plugin PrivatusAnalytics => {
site_id => 'pa_YOURSITEID', # or $ENV{PRIVATUS_SITE_ID}
script_host => 'https://privatusstats.com', # or $ENV{PRIVATUS_SCRIPT_HOST}
modules => [qw(engage auto vitals clicks ask)],
excluded_paths => [qw(/admin/* /preview/*)],
domains => [qw(example.com www.example.com)],
spa => 'auto', # auto, history, hash or off
manual => 0, # 1 turns off the automatic pageview
auto_inject => 0,
extra_attributes => { 'data-mask' => '/orders/*->/orders/:id' },
props => sub { my ($c) = @_; { plan => $c->stash('plan') } },
queue_stub => 0,
};
```
| Setting | Default | What it does |
|---|---|---|
| `site_id` | `$ENV{PRIVATUS_SITE_ID}` | The `data-site` attribute. Nothing prints without it |
| `script_host` | `https://privatusstats.com` | Where `/js/pa.js` is served. Change it only if you host your own copy |
| `modules` | tracker default (`engage`) | The `data-modules` attribute. Setting it replaces the default, so keep `engage` for time on page and scroll depth. `[]` loads none |
| `excluded_paths` | none | The `data-exclude` attribute. Path patterns the tracker never counts |
| `domains` | all hosts | The `data-domains` attribute. Count only these hostnames |
| `spa` | `auto` | The `data-spa` attribute, for single-page apps |
| `manual` | `0` | The `data-manual` attribute |
| `enabled` | `undef` | `1` or `0` forces the tag on or off in every environment |
| `enabled_environments` | `['production']` | Environments that get the tag. The Plack middleware uses `['deployment', 'production']` |
| `environment` | the framework's | The current environment. Mojolicious uses the app mode, Dancer2 its environment, and the rest `$ENV{PLACK_ENV}` |
| `auto_inject` | `0` | Adds the tag to the head of HTML pages. On by default in the Plack middleware |
| `extra_attributes` | `{}` | Any other `data-*` attribute |
| `props` | `{}` | Page props sent with every pageview and event. A hash ref, or a code ref that gets the request. See below |
| `queue_stub` | `0` | Prints a small script first, so `privatus.track` calls made before the tracker loads are kept and sent later. It still prints when the tag is off, so those calls never fail |
| `nonce` | none | The Content Security Policy nonce. A string, or a code ref that gets the request |
Lists can be array refs or comma separated text. Each tracker attribute is
explained in the [script attributes
reference](https://privatusanalytics.com/docs/tracker/attributes), and the
modules in [Modules](https://privatusanalytics.com/docs/tracker/modules).
You can also override a setting for one page:
```
%= privatus_analytics_tag modules => [qw(engage auto)], excluded_paths => ['/preview/*']
```
### Development and test
Development and test requests are not counted by default, so your own
clicking around never reaches your reports. The tag is on when:
* Mojolicious: the app mode is `production`.
* Dancer2: the environment is `production`.
* Plack: `PLACK_ENV` is `deployment` or `production`.
To see the tag while you build, set `enabled => 1`. The tracker also ignores
`localhost` unless you pass `data-allow-local` or `data-debug` (see [Debug
mode](https://privatusanalytics.com/docs/tracker/debug)):
```perl
extra_attributes => { 'data-debug' => 'true' }
```
### Leaving people out
Use `data-exclude` through `excluded_paths` for admin areas. To leave out
signed-in staff, print the helper only for other people:
```
%= privatus_analytics_tag unless current_user_is_admin
```
## Product analytics: see which features people use
Privatus can show which buttons, forms and features people really use. You
add a few attributes to your HTML. The tracker reads them in the browser and
sends the events. Your server still sends nothing.
### Name events well
Use a short "Noun verb" name, like `Report exported` or `Invite sent`. Put the
details in props, not in the name. So write `Report exported` with
`format => 'csv'`, not `Report exported as CSV`. Then all exports add up in
one place, and you can split them by format.
### Events on buttons, links and forms
`privatus_event` prints the attributes inside a tag you write yourself.
Mojolicious:
```
```
Dancer2 (Template Toolkit):
```
```
Plain Perl:
```perl
use PrivatusAnalytics qw(event_attrs click_attr);
my $attrs = event_attrs('Report exported', { format => 'csv', source => 'toolbar' });
```
They print:
```html
```
Props can be a hash ref (the keys are sorted), an array ref of pairs, or a
plain list of pairs (both keep your order). With the Mojolicious `tag` and
`link_to` helpers, use `privatus_event_data`. It returns the attributes as a
list of pairs:
```
%= link_to 'Read the guide' => '/guide', privatus_event_data('Guide opened')
```
On a form, the event is sent when the form is submitted:
```