Todas as docs

Início

  1. 01Primeiros passos
  2. 02Instalação
  3. 03Rastreamento

Recursos

  1. 04O painel
  2. 05Metas
  3. 06Links curtos
  4. 07Origens e agentes de IA
  5. 08Relatórios por email
  6. 09Verificação agendada
  7. 10Pergunte à sua IA

Referência

  1. 11Configuração
  2. 12API HTTP

Plataformas

  1. 13Servidor independente
  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

Confiança

  1. 26Privacidade

Plataformas

Python

O Runlight roda em Python 3.11 ou mais recente, dentro de um app Django, Flask ou FastAPI ou de qualquer app WSGI ou ASGI, ou como servidor independente.

O pacote Python é o Runlight reescrito em Python. Ele serve o mesmo painel e a mesma API, e dá a cada requisição a mesma resposta que a biblioteca TypeScript dá. Os números vão para as mesmas tabelas, então qualquer um dos dois consegue ler um banco de dados que o outro escreveu. Ele precisa do Python 3.11 ou mais recente e não tem dependências próprias.

Instalação

Adicione o pacote com o pip.

bash

pip install runlight

O SQLite vem com o Python. Para Postgres, instale o extra postgres, que adiciona o psycopg 3, e para MySQL ou MariaDB, instale o extra mysql, que adiciona o PyMySQL.

bash

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

Os fusos horários vêm do banco de dados de fusos horários do sistema. O Windows não tem um, então adicione o extra tzdata nele.

Crie a instância

Crie um módulo que cria a instância e importe-o onde você precisar do 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"},
)

As opções têm os mesmos nomes que em Configuração. Passe-as como argumentos nomeados em snake case, como trust_proxy=False e rate_limit=60, ou como um único dict com os nomes do TypeScript, como Runlight({"store": ..., "trustProxy": False}). As tabelas são criadas na primeira requisição.

Stores

As tabelas do Runlight começam com rl_. Todo store aceita prefix para usar outro prefixo no lugar, como Stores.sqlite(path, prefix="wp_rl_"), de modo que dois apps que compartilham um banco de dados mantêm cada um as suas próprias tabelas. Um prefixo tem até 32 letras, dígitos e sublinhados e não começa com dígito. store.tables() lista as tabelas do store pelo nome, para um app que esteja removendo o Runlight.

SQLite

python

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

Mantenha o arquivo fora de qualquer pasta que o seu servidor web sirva, em uma pasta em que o app possa escrever, já que o SQLite grava um journal ao lado dele. A pasta é criada se não existir.

Postgres

python

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

statement_timeout interrompe qualquer comando depois desse número de milissegundos, e o padrão é 120000. schema mantém as tabelas em um schema próprio, como em Stores.postgres(url, schema="analytics"). No lugar de uma URL, você pode passar uma conexão psycopg que o seu app já tenha, que o Runlight usa como está e nunca fecha.

MySQL e MariaDB

python

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

Este store funciona com MySQL 8.0.20 ou mais recente e MariaDB 11.4 ou mais recente. Uma URL mariadb:// também funciona. Ele aceita statement_timeout como o Postgres, que o MySQL aplica às leituras e o MariaDB a todos os comandos, como em Configuração.

Stores.url() escolhe o store a partir de uma DATABASE_URL que comece com postgres://, mysql://, mariadb:// ou sqlite:.

Django

Adicione o app do Runlight e o middleware dele às suas configurações, e indique a instância em RUNLIGHT. O middleware vem primeiro, para que nenhum outro middleware altere as requisições do Runlight nem as respostas dele.

settings.py

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

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

RUNLIGHT = "myproject.analytics.rl"

RUNLIGHT guarda a própria instância ou o caminho pontuado até ela, e um caminho para uma função que cria uma também funciona. RUNLIGHT_ROUTES passa opções para as rotas com os nomes do TypeScript, como {"accounts": True}. O middleware serve o painel e a API sob /runlight, os links curtos em /go e todos os domínios de links que você adiciona em Configurações, antes das URLs do próprio Django. Cada domínio de links também precisa estar em ALLOWED_HOSTS.

Rode a verificação agendada pelo cron com o comando de gerenciamento.

bash

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

Flask

Passe o app e a instância para init_app, que coloca o Runlight na frente das views do próprio app. Ele responde sob /runlight, em /go e em todos os domínios de links que você adiciona em Configurações, e passa todo o resto para o seu app.

app.py

from flask import Flask

import runlight.flask
from myproject.analytics import rl

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

As opções das rotas entram como argumentos nomeados, como runlight.flask.init_app(app, rl, accounts=True). init_app também adiciona um comando que roda a verificação agendada, para o cron.

bash

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

FastAPI

init_app adiciona o Runlight como middleware na frente das rotas do app, então ele responde sob /runlight, em /go e em todos os domínios de links que você adiciona em Configurações. Funciona também para apps Starlette.

main.py

from fastapi import FastAPI

import runlight.fastapi
from myproject.analytics import rl

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

O trabalho do próprio Runlight roda em threads de worker, então ele nunca bloqueia o event loop. Para a verificação agendada, chame rl.check() a partir de um script que o cron executa, ou defina CRON_SECRET e chame a verificação por HTTP.

Qualquer app WSGI ou ASGI

Bottle, Pyramid e qualquer outro app WSGI usam o middleware WSGI.

python

from runlight.wsgi import RunlightMiddleware

application = RunlightMiddleware(application, rl)

Quart, Django sob ASGI e qualquer outro app ASGI usam o middleware ASGI, que recebe a instância como runlight=.

python

from runlight.asgi import RunlightMiddleware

application = RunlightMiddleware(application, runlight=rl)

Os dois respondem sob /runlight, em /go e em todos os domínios de links, e passam todo o resto para o seu app. As opções das rotas entram como argumentos nomeados, como no Flask. Para rodar o Runlight como um app próprio, sem um app seu por trás, use runlight.wsgi.app(rl) com um servidor WSGI como o gunicorn, ou runlight.asgi.app(rl) com um servidor ASGI como o uvicorn. Os caminhos fora dos do Runlight respondem então com 404.

Um link curto em /go/<name> sem nenhum link com esse nome segue para o seu app, então a sua própria página 404 responde, seja a do Django, a do Flask, a do FastAPI ou a do seu app WSGI ou ASGI. runlight.wsgi.app() e runlight.asgi.app() não têm nenhum app por trás e respondem eles mesmos com um 404. Num domínio de links, /go/<name> é um 404 como qualquer outro caminho que não seja um dos links desse domínio.

Seja qual for o jeito como você o adiciona, coloque o script em todas as páginas, logo antes de </head>.

html

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

Depois defina RUNLIGHT_TOKEN como uma string longa e aleatória e abra /runlight/?token= seguido dessa string uma vez para entrar, como descreve Primeiros passos. Com accounts=True e um RUNLIGHT_SECRET, as pessoas entram com o próprio e-mail e senha, como em Contas.

Agentes de IA

O middleware registra as leituras de agentes de IA enquanto o seu app serve as páginas, e deixa passar intacta qualquer outra requisição. Passe observe=False para init_app ou para o middleware para desativar isso, ou defina "observe": False em RUNLIGHT_ROUTES no Django. Sem middleware, chame rl.observe(request) com um runlight.http.Request para cada requisição de página.

A verificação agendada

A verificação agendada troca os salts diários, envia os relatórios por email que estão na hora, aplica por quanto tempo cada site guarda as visitas e soma os totais diários que deixam os períodos longos rápidos. Rode-a a cada poucos minutos com python manage.py runlight_check no Django, flask runlight check no Flask ou rl.check() a partir de qualquer script. Quando nada consegue rodar um comando em um timer, defina CRON_SECRET e faça um agendador chamar /runlight/api/check com ele, como mostra Verificação agendada.

O servidor independente

O pacote inclui o servidor independente, que roda o Runlight em um domínio próprio, como stats.example.com, com o painel na raiz, sites adicionados pelo painel, contas com login e links curtos em qualquer domínio que você apontar para ele. Instale o pacote e rode o comando runlight.

bash

pip install runlight
runlight

O servidor escuta na porta 3000 e guarda os dados em uma pasta chamada runlight-data no diretório de onde você o inicia. Na primeira inicialização, ele mostra um link de configuração com um código de uso único. Abra esse link e crie sua conta, como descreve Crie sua conta. O servidor roda a verificação agendada sozinho a cada cinco minutos, e baixa os dados de localização do mês como o servidor Node faz. Coloque-o atrás de um proxy que adicione HTTPS, como em Coloque na internet.

Configurações

O servidor lê as configurações de variáveis de ambiente.

Variável O que faz
PORT A porta em que escutar. O padrão é 3000.
HOST O endereço em que escutar. O padrão é 0.0.0.0.
DATA_DIR A pasta do arquivo SQLite, do segredo e dos dados de localização. O padrão é ./runlight-data.
DATABASE_URL Um endereço postgres:// guarda os dados no Postgres em vez do SQLite, e um endereço mysql:// ou mariadb:// os guarda no MySQL ou no MariaDB.
RUNLIGHT_SECRET A chave que assina os logins e criptografa as chaves salvas. Sem ela, o servidor cria uma e a guarda em DATA_DIR, legível só pelo usuário que o roda.
RUNLIGHT_URL O endereço público do servidor, como https://stats.example.com, que nunca pode virar um domínio de links. Os e-mails de convite e de relatório apontam para ele.
RUNLIGHT_TOKEN Um token que scripts podem enviar como bearer, além dos tokens de API criados no painel.
RUNLIGHT_OBSERVE_KEY Uma única chave com que qualquer site pode informar agentes de IA. A chave de agentes própria de cada site, mostrada em Configurações, Instalação, é a melhor escolha.
TRUST_PROXY Defina como false quando não há nenhum proxy na frente do servidor, ou como x-real-ip ou cf-connecting-ip quando esse cabeçalho traz o endereço do visitante.
RUNLIGHT_GEO De onde vêm as localizações quando nenhum cabeçalho as informa, como em Localizações. O padrão é city, e pode ser country, off ou o caminho de um arquivo MMDB.
CRON_SECRET Permite que um agendador rode a verificação em POST /api/check.

Comandos

runlight check roda a verificação agendada uma vez e baixa os dados de localização do mês. runlight migrate cria ou atualiza as tabelas na hora. As tabelas também se atualizam sozinhas quando o servidor inicia, então uma atualização só precisa de pip install --upgrade runlight e um reinício. Faça backup da pasta de dados, já que as chaves salvas não podem ser lidas sem o segredo que está nela.

Senhas esquecidas

Rode o comando password onde o servidor roda para dar uma nova senha a uma conta, que ele mostra. Ele também desativa o login em duas etapas dessa conta, e cria a conta quando ela não existe, como proprietário em um servidor ainda sem ninguém e como administrador nos outros casos.

bash

runlight password someone@example.com

Agentes de IA a partir de um log

Agentes de IA como o GPTBot e o ClaudeBot leem páginas sem rodar JavaScript, então a tag do script nunca os vê. Um app Python com o Runlight dentro os conta onde a página é servida, como mostra Agentes de IA. Para qualquer outro site que você hospeda, o log de acesso do servidor web os registra, e o comando agents lê esse log.

bash

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

A chave é a chave de agentes do site, mostrada no Runlight em Configurações, Instalação, e ela só pode informar leituras desse site. Quando --to ou --key ficam de fora, o comando lê RUNLIGHT_URL ou RUNLIGHT_OBSERVE_KEY do ambiente. O comando funciona como no servidor independente, com --follow para continuar rodando e --state para continuar de onde a última execução parou. Só as leituras de página bem-sucedidas de agentes de IA conhecidos saem da máquina, e os endereços dos visitantes ficam onde estão.

Diferenças em relação à biblioteca TypeScript

O pacote Python passa nos testes de conformidade a que a biblioteca TypeScript é submetida, em SQLite, Postgres, MySQL e MariaDB. As diferenças vêm do jeito como o Python roda.

  • O trabalho do Runlight é síncrono e roda em threads. O middleware ASGI entrega cada uma das suas requisições a uma thread de worker, então o event loop fica livre para o resto do seu app.
  • O SQLite mantém uma conexão, que todas as threads compartilham sob um lock. O Postgres e o MySQL abrem uma conexão para cada thread, então um servidor com dezesseis threads mantém até dezesseis conexões.
  • O que a biblioteca TypeScript faz em segundo plano, como apagar visitas depois que uma retenção menor é escolhida, roda depois que a resposta é enviada.
  • O limite de requisições do script de rastreamento e as listas em cache de domínios de links e de ícones dos sites ficam na memória, então cada processo mantém as suas. Um servidor com vários processos de worker permite a cada endereço o limite uma vez em cada processo. Os limites de senha errada ficam no banco de dados, então todos os processos os compartilham.
  • As senhas recebem hash com scrypt pelo hashlib do Python. Os hashes batem com os da biblioteca TypeScript, então as contas são aproveitadas.