Identity & signed-in

Two facts only your site can know, and Loghound will never guess either of them.

Two facts only your site can know, and Loghound will not invent either: what you call the person, and whether they were signed in.

They are independent, and that is the point

You may pass an identity for a visitor who is not authenticated. You may want “this was a signed-in session” recorded without handing over who it was. Neither implies the other and neither is derived from the other.

Loghound never guesses either value. No cookie is read, no form is scraped, no meta tag is looked for, no variable is hunted through. If your site does not say, the field does not exist on the session.

01 · Route 1: attributes, when your server knows at render time

The normal case. The template that renders the page renders the tag, in the same response: no second request, no extra script, no ordering problem.

<script src="https://loghound.example.com/b.js?v=1"
        data-ident="ada@example.com" data-signed-in="1" defer></script>

You would not write the address literally, of course — it comes out of whatever object your framework already has.

WordPress

<?php
// wp-content/mu-plugins/loghound.php - a must-use plugin, so it survives a theme change.
add_action('wp_head', static function (): void {
    $attrs = ' data-signed-in="0"';
    if (is_user_logged_in()) {
        $attrs = ' data-ident="' . esc_attr(wp_get_current_user()->user_email) . '"'
            . ' data-signed-in="1"';
    }
    echo '<script src="https://loghound.example.com/b.js?v=1"' . $attrs . ' defer></script>' . "\n";
}, 99);

A signed-out visitor gets data-signed-in="0" and no data-ident at all — which is right: they are anonymous, and that is a different statement from “we were not told”. esc_attr() is what stops an address containing a quote from breaking the tag.

If you run a page cache

WP Rocket, W3 Total Cache, LiteSpeed and Cloudflare APO all cache the rendered tag with the page, so the first visitor’s identity would be served to everybody else. Exclude logged-in users from the cache — every one of those plugins does so by default — or use route 3 below from an uncached request.

Drupal

<?php
# your_theme.theme  (or a small custom module)
function your_theme_page_attachments(array &$attachments): void {
  $account = \Drupal::currentUser();

  $tag = [
    '#type' => 'html_tag',
    '#tag' => 'script',
    '#attributes' => [
      'src' => 'https://loghound.example.com/b.js?v=1',
      'defer' => TRUE,
      'data-signed-in' => $account->isAuthenticated() ? '1' : '0',
    ],
  ];
  if ($account->isAuthenticated()) {
    $tag['#attributes']['data-ident'] = $account->getEmail();
  }

  $attachments['#attached']['html_head'][] = [$tag, 'loghound'];
  $attachments['#cache']['contexts'][] = 'user';
}

html_head rather than a library, because a library is declared once in YAML and cannot carry a value that changes per request. The user cache context is not optional: without it Drupal’s render cache serves the first authenticated visitor’s address to every other one. Use getAccountName() instead of getEmail() if a username is the identifier you want, and run drush cr afterwards.

02 · Route 2: globals, for a tag manager or a template you cannot edit

They must be set before b.js executes, which with defer means anywhere in the document.

<script>window.LoghoundIdent = "ada@example.com"; window.LoghoundSignedIn = true;</script>
<script src="https://loghound.example.com/b.js?v=1" defer></script>

In Google Tag Manager that is a Custom HTML tag with data layer variables, because a tag manager runs in the browser and cannot know who is signed in — the value has to reach it from your own page:

<script>
  window.LoghoundIdent = "{{Loghound Ident}}";
  window.LoghoundSignedIn = "{{Loghound Signed In}}";
</script>
<script src="https://loghound.example.com/b.js?v=1" defer></script>
03 · Route 3: when the identity arrives after load

A single-page application that signs somebody in without a navigation, which no attribute can express:

window.loghound.identify('ada@example.com', true);

It makes no request of its own: the values ride the heartbeat that is already scheduled, so attaching an identity costs your site nothing extra. Both arguments are optional and independent, so identify(null, true) records the signed-in state and no identity.

04 · Three states, not two

The signed-in field on a session is written only when your site actually said one or the other. A site that never sends the attribute leaves the field absent, and the panel reports that population as “not reported” — never as anonymous.

SIGNED INthe site said yesdata-signed-in="1"ANONYMOUSthe site said nodata-signed-in="0"NOT REPORTEDthe site said nothingthe field is absentA boolean defaulting to false would invent an anonymous populationout of every site that never adopted the attribute.

Figure 1 — three states. The split between signed-in and anonymous traffic is the reason the field exists; counting “not reported” as anonymous would make that split a fabrication over traffic nobody classified.

Where several payloads in one session disagree, the resolution follows what happened: the last non-empty identity wins, because somebody who signs in halfway through a visit is that person by the end of it; and signed-in beats anonymous, because a session that was authenticated at any point was an authenticated session.

05 · Storage, and the two switches
SettingDefaultWhat it stores
beacon.store_identityfalseThe string your site sent, at most 128 bytes, on the session document
beacon.store_signed_intrueA boolean on the session document, or nothing when unreported
Identity storage is off by default, and that is a policy default rather than an oversight

Everything else this beacon collects is a measurement of a browser. This is a name. With it on, an address is written to the session document, appears in the panel, lives in the Solr index, and is in every backup of that index until retention deletes the session. With it off, the string is discarded before anything is written — it never reaches the staging database, let alone Solr. Switching it off stops collection; it does not merely hide what was collected.

The signed-in switch is on by default because the boolean identifies nobody, and because the split it enables — engaged time, paths, bounce and bot verdict for signed-in versus anonymous traffic — is one of the most useful things the panel can show. The two switches are independent: storing the split while storing no identities at all is a perfectly ordinary configuration, and probably the right one for most sites.

In the panel, both appear on the session detail dialog in a strip flagged with the accent, because they are the only facts there that Loghound did not derive for itself. The identity string is rendered as text at every sink like every other value that came off the wire — it is caller-supplied input on a public endpoint, and it is treated as hostile regardless of how trustworthy the site that sent it is.

Loghound is open source and MIT licensed. Questions about the Opensolr half — the account, the indexes, the plan — go to opensolr.com/contact; questions about the software itself belong on GitHub.

Loghound Documentation