Toute la documentation

Démarrer

  1. 01Pour commencer
  2. 02Installation
  3. 03Suivi

Fonctionnalités

  1. 04Le tableau de bord
  2. 05Objectifs
  3. 06Liens courts
  4. 07Sources et agents IA
  5. 08Rapports par e-mail
  6. 09Vérification planifiée
  7. 10Interrogez votre IA

Référence

  1. 11Configuration
  2. 12API HTTP

Plateformes

  1. 13Serveur autonome
  2. 14PHP
  3. 15WordPress
  4. 16Drupal
  5. 17Craft CMS
  6. 18Python
  7. 19Rails
  8. 20Ruby
  9. 21Go
  10. 22Java
  11. 23.NET
  12. 24Elixir
  13. 25Rust

Confiance

  1. 26Confidentialité

Plateformes

Python

Runlight tourne sous Python 3.11 ou plus récent, dans une application Django, Flask ou FastAPI ou n’importe quelle application WSGI ou ASGI, ou comme serveur à part entière.

Le paquet Python, c’est Runlight réécrit en Python. Il sert le même tableau de bord et la même API, et il donne à chaque requête la même réponse que la bibliothèque TypeScript. Ses chiffres vont dans les mêmes tables, donc chacun peut lire une base de données écrite par l’autre. Il lui faut Python 3.11 ou plus récent et il n’a aucune dépendance propre.

Installation

Ajoutez le paquet avec pip.

bash

pip install runlight

SQLite est fourni avec Python. Pour Postgres, installez l’extra postgres, qui ajoute psycopg 3, et pour MySQL ou MariaDB, installez l’extra mysql, qui ajoute PyMySQL.

bash

pip install "runlight[postgres]"
pip install "runlight[mysql]"

Les fuseaux horaires viennent de la base de fuseaux horaires du système. Windows n’en a pas, donc ajoutez l’extra tzdata sous Windows.

Créer l’instance

Créez un module qui crée l’instance, et importez-le partout où vous avez besoin de Runlight.

myproject/analytics.py

from runlight import Runlight
from runlight.store import Stores

rl = Runlight(
    store=Stores.sqlite("data/runlight.db"),
    site={"name": "example.com", "hostnames": ["example.com"], "timezone": "Europe/London"},
)

Les options portent les mêmes noms que dans Configuration. Passez-les comme arguments nommés en snake case, comme trust_proxy=False et rate_limit=60, ou dans un seul dict avec les noms TypeScript, comme Runlight({"store": ..., "trustProxy": False}). Les tables sont créées à la première requête.

Stockages

Les tables de Runlight commencent par rl_. Chaque stockage accepte prefix à utiliser à la place, comme Stores.sqlite(path, prefix="wp_rl_"), pour que deux applications qui partagent une base gardent chacune leurs propres tables. Un préfixe compte jusqu’à 32 lettres, chiffres et tirets bas, et ne commence pas par un chiffre. store.tables() liste les tables du stockage par leur nom, pour une application qui retire Runlight.

SQLite

python

Stores.sqlite("data/runlight.db")

Gardez le fichier hors de tout dossier que sert votre serveur web, dans un dossier où l’application peut écrire, puisque SQLite écrit un journal à côté. Le dossier est créé s’il manque.

Postgres

python

Stores.postgres("postgres://runlight:password@127.0.0.1:5432/runlight")

statement_timeout arrête toute instruction au bout de ce nombre de millisecondes, 120000 par défaut. schema garde les tables dans leur propre schéma, comme dans Stores.postgres(url, schema="analytics"). À la place d’une URL, vous pouvez passer une connexion psycopg que votre application a déjà, que Runlight utilise telle quelle et ne ferme jamais.

MySQL et MariaDB

python

Stores.mysql("mysql://runlight:password@127.0.0.1:3306/runlight")

Ce stockage fonctionne avec MySQL 8.0.20 ou plus récent et MariaDB 11.4 ou plus récent. Une URL mariadb:// fonctionne aussi. Il accepte statement_timeout comme Postgres, que MySQL applique aux lectures et MariaDB à chaque instruction, comme dans Configuration.

Stores.url() choisit le stockage d’après un DATABASE_URL qui commence par postgres://, mysql://, mariadb:// ou sqlite:.

Django

Ajoutez l’application de Runlight et son middleware à vos settings, et nommez l’instance dans RUNLIGHT. Le middleware vient en premier, pour qu’aucun autre middleware ne modifie les requêtes de Runlight ou ses réponses.

settings.py

INSTALLED_APPS = [
    # ...
    "runlight.django",
]

MIDDLEWARE = [
    "runlight.django.RunlightMiddleware",
    # ...
]

RUNLIGHT = "myproject.analytics.rl"

RUNLIGHT contient l’instance elle-même ou son chemin pointé, et le chemin d’une fonction qui en crée une fonctionne aussi. RUNLIGHT_ROUTES passe des options aux routes avec leurs noms TypeScript, comme {"accounts": True}. Le middleware sert le tableau de bord et l’API sous /runlight, les liens courts sur /go et chaque domaine de liens que vous ajoutez dans Réglages, avant les URL de Django. Chaque domaine de liens doit aussi figurer dans ALLOWED_HOSTS.

Lancez la vérification planifiée depuis cron avec la commande de gestion.

bash

*/5 * * * * cd /srv/myproject && python manage.py runlight_check

Flask

Passez l’application et l’instance à init_app, qui place Runlight devant les vues de l’application. Il répond sous /runlight, sur /go et sur chaque domaine de liens que vous ajoutez dans Réglages, et passe tout le reste à votre application.

app.py

from flask import Flask

import runlight.flask
from myproject.analytics import rl

app = Flask(__name__)
runlight.flask.init_app(app, rl)

Les options des routes se passent comme arguments nommés, comme runlight.flask.init_app(app, rl, accounts=True). init_app ajoute aussi une commande qui lance la vérification planifiée, pour cron.

bash

*/5 * * * * cd /srv/myproject && flask --app app runlight check

FastAPI

init_app ajoute Runlight comme middleware devant les routes de l’application, donc il répond sous /runlight, sur /go et sur chaque domaine de liens que vous ajoutez dans Réglages. Il fonctionne aussi pour les applications Starlette.

main.py

from fastapi import FastAPI

import runlight.fastapi
from myproject.analytics import rl

app = FastAPI()
runlight.fastapi.init_app(app, rl)

Le travail de Runlight s’exécute dans des threads de travail, donc il ne bloque jamais la boucle d’événements. Pour la vérification planifiée, appelez rl.check() depuis un script lancé par cron, ou définissez CRON_SECRET et appelez la vérification en HTTP.

N’importe quelle application WSGI ou ASGI

Bottle, Pyramid et toute autre application WSGI prennent le middleware WSGI.

python

from runlight.wsgi import RunlightMiddleware

application = RunlightMiddleware(application, rl)

Quart, Django sous ASGI et toute autre application ASGI prennent le middleware ASGI, qui reçoit l’instance avec runlight=.

python

from runlight.asgi import RunlightMiddleware

application = RunlightMiddleware(application, runlight=rl)

Les deux répondent sous /runlight, sur /go et sur chaque domaine de liens, et passent tout le reste à votre application. Les options des routes se passent comme arguments nommés, comme avec Flask. Pour faire tourner Runlight comme une application à part entière, sans l’une des vôtres derrière, utilisez runlight.wsgi.app(rl) avec un serveur WSGI comme gunicorn, ou runlight.asgi.app(rl) avec un serveur ASGI comme uvicorn. Les chemins hors de ceux de Runlight répondent alors 404.

Un lien court sur /go/<name> sans lien de ce nom continue vers votre application, donc votre propre page 404 y répond, que ce soit celle de Django, de Flask, de FastAPI ou de votre application WSGI ou ASGI. runlight.wsgi.app() et runlight.asgi.app() n’ont aucune application derrière eux et y répondent eux-mêmes avec un 404. Sur un domaine de liens, /go/<name> est un 404 comme tout autre chemin qui n’est pas l’un des liens de ce domaine.

Quelle que soit la façon dont vous l’ajoutez, mettez le script sur chaque page, juste avant </head>.

html

<script defer src="/runlight/s.js"></script>

Définissez ensuite RUNLIGHT_TOKEN avec une longue chaîne aléatoire et ouvrez une fois /runlight/?token= suivi de cette chaîne pour vous connecter, comme l’explique Pour commencer. Avec accounts=True et un RUNLIGHT_SECRET, chacun se connecte avec son propre e-mail et son propre mot de passe, comme dans Comptes.

Agents IA

Le middleware enregistre les requêtes des agents IA pendant que votre application sert ses pages, et laisse passer toutes les autres requêtes sans y toucher. Passez observe=False à init_app ou au middleware pour le désactiver, ou mettez "observe": False dans RUNLIGHT_ROUTES avec Django. Sans middleware, appelez rl.observe(request) avec un runlight.http.Request pour chaque requête de page.

La vérification planifiée

La vérification planifiée renouvelle les sels quotidiens, envoie les rapports par e-mail arrivés à échéance, applique la durée de conservation des visites de chaque site et construit les agrégats qui gardent les longues périodes rapides. Lancez-la toutes les quelques minutes avec python manage.py runlight_check dans Django, flask runlight check dans Flask, ou rl.check() depuis n’importe quel script. Quand rien ne peut lancer une commande à intervalles réguliers, définissez CRON_SECRET et faites appeler /runlight/api/check avec lui par un planificateur, comme le montre Vérification planifiée.

Le serveur autonome

Le paquet inclut le serveur autonome, qui fait tourner Runlight sur son propre domaine, comme stats.example.com, avec le tableau de bord à la racine, des sites ajoutés depuis le tableau de bord, des comptes de connexion et des liens courts sur n’importe quel domaine que vous faites pointer vers lui. Installez le paquet et lancez la commande runlight.

bash

pip install runlight
runlight

Le serveur écoute sur le port 3000 et garde ses données dans un dossier nommé runlight-data dans le répertoire depuis lequel vous le lancez. À son premier démarrage, il affiche un lien de configuration avec un code à usage unique. Ouvrez ce lien et créez votre compte, comme l’explique Créer votre compte. Le serveur lance lui-même la vérification planifiée toutes les cinq minutes, et il télécharge les données de localisation du mois comme le fait le serveur Node. Placez-le derrière un proxy qui ajoute HTTPS, comme dans Le mettre sur Internet.

Réglages

Le serveur lit ses réglages dans des variables d’environnement.

Variable Ce qu’elle fait
PORT Le port d’écoute. 3000 par défaut.
HOST L’adresse d’écoute. 0.0.0.0 par défaut.
DATA_DIR Le dossier du fichier SQLite, du secret et des données de localisation. ./runlight-data par défaut.
DATABASE_URL Une adresse postgres:// garde les données dans Postgres au lieu de SQLite, et une adresse mysql:// ou mariadb:// les garde dans MySQL ou MariaDB.
RUNLIGHT_SECRET La clé qui signe les connexions et chiffre les clés enregistrées. Sans elle, le serveur en crée une et la garde dans DATA_DIR, lisible uniquement par l’utilisateur qui le fait tourner.
RUNLIGHT_URL L’adresse publique du serveur, par exemple https://stats.example.com, qui ne peut jamais devenir un domaine de liens. Les e-mails d’invitation et de rapport pointent vers elle.
RUNLIGHT_TOKEN Un jeton que des scripts peuvent envoyer comme bearer token, en plus des jetons d’API créés dans le tableau de bord.
RUNLIGHT_OBSERVE_KEY Une seule clé pour les signalements d’agents IA de tous les sites. La clé des agents propre à chaque site, affichée dans Réglages, Installation, est le meilleur choix.
TRUST_PROXY Mettez false quand aucun proxy ne se trouve devant le serveur, ou x-real-ip ou cf-connecting-ip quand c’est cet en-tête qui contient l’adresse du visiteur.
RUNLIGHT_GEO D’où viennent les lieux quand aucun en-tête ne les donne, comme dans Lieux. city par défaut, et peut valoir country, off ou le chemin d’un fichier MMDB.
CRON_SECRET Permet à un planificateur de lancer la vérification sur POST /api/check.

Commandes

runlight check lance une fois la vérification planifiée et récupère les données de localisation du mois. runlight migrate crée ou met à jour les tables tout de suite. Les tables se mettent aussi à jour toutes seules au démarrage du serveur, donc une mise à jour ne demande que pip install --upgrade runlight et un redémarrage. Sauvegardez le dossier de données, puisque les clés enregistrées ne peuvent pas être lues sans le secret qu’il contient.

Mots de passe oubliés

Lancez la commande password là où tourne le serveur pour donner à un compte un nouveau mot de passe, qu’elle affiche. Elle désactive aussi la connexion à deux facteurs pour ce compte, et elle crée le compte s’il n’existe pas, comme propriétaire sur un serveur encore vide et comme administrateur sinon.

bash

runlight password someone@example.com

Agents IA depuis un journal

Les agents IA comme GPTBot et ClaudeBot récupèrent les pages sans exécuter de JavaScript, donc la balise script ne les voit jamais. Une application Python qui contient Runlight les compte là où la page est servie, comme le montre Agents IA. Pour tout autre site que vous hébergez vous-même, le journal d’accès du serveur web les contient, et la commande agents le lit.

bash

runlight agents --log /var/log/nginx/access.log --to https://stats.example.com --key rlo_... --site https://example.com --follow

La clé est la clé des agents du site, affichée dans Runlight dans Réglages, Installation, et elle ne peut signaler que les requêtes de ce site. Quand --to ou --key est omis, la commande lit RUNLIGHT_URL ou RUNLIGHT_OBSERVE_KEY dans l’environnement. La commande fonctionne comme sur le serveur autonome, avec --follow pour continuer de tourner et --state pour reprendre là où la dernière exécution s’est arrêtée. Seules les requêtes de pages réussies des agents IA connus quittent la machine, et les adresses des visiteurs restent où elles sont.

Différences avec la bibliothèque TypeScript

Le paquet Python passe les tests de conformité imposés à la bibliothèque TypeScript, sur SQLite, Postgres, MySQL et MariaDB. Les différences viennent de la façon dont Python s’exécute.

  • Le travail de Runlight est synchrone et s’exécute dans des threads. Le middleware ASGI confie chacune de ses requêtes à un thread de travail, donc la boucle d’événements reste libre pour le reste de votre application.
  • SQLite garde une seule connexion, que tous les threads partagent sous un verrou. Postgres et MySQL ouvrent une connexion par thread, donc un serveur à seize threads garde jusqu’à seize connexions.
  • Ce que la bibliothèque TypeScript fait en arrière-plan, comme supprimer des visites après le choix d’une durée de conservation plus courte, s’exécute une fois la réponse envoyée.
  • La limite de débit du traceur et les listes en cache des domaines de liens et des icônes de sites sont gardées en mémoire, donc chaque processus a les siennes. Un serveur à plusieurs processus de travail accorde la limite à chaque adresse une fois dans chaque processus. Les limites de mots de passe erronés sont gardées dans la base, donc tous les processus les partagent.
  • Les mots de passe sont hachés avec scrypt par le hashlib de Python. Les hachages sont identiques à celles de la bibliothèque TypeScript, donc les comptes se transfèrent.