Toda la documentación

Empezar

  1. 01Primeros pasos
  2. 02Instalación
  3. 03Seguimiento

Funciones

  1. 04El panel
  2. 05Objetivos
  3. 06Enlaces cortos
  4. 07Fuentes y agentes de IA
  5. 08Informes por email
  6. 09Comprobación programada
  7. 10Pregunta a tu IA

Referencia

  1. 11Configuración
  2. 12API HTTP

Plataformas

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

Confianza

  1. 26Privacidad

Plataformas

Python

Runlight funciona con Python 3.11 o posterior, dentro de una aplicación Django, Flask o FastAPI o de cualquier aplicación WSGI o ASGI, o como servidor independiente.

El paquete de Python es Runlight escrito de nuevo en Python. Sirve el mismo panel y la misma API, y da a cada petición la misma respuesta que da la biblioteca de TypeScript. Sus cifras van en las mismas tablas, así que cualquiera de los dos puede leer una base de datos que haya escrito el otro. Necesita Python 3.11 o posterior y no tiene dependencias propias.

Instalación

Añade el paquete con pip.

bash

pip install runlight

SQLite viene con Python. Para Postgres, instala el extra postgres, que añade psycopg 3, y para MySQL o MariaDB, instala el extra mysql, que añade PyMySQL.

bash

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

Las zonas horarias salen de la base de datos de zonas horarias del sistema. Windows no tiene ninguna, así que ahí añade el extra tzdata.

Crea la instancia

Crea un módulo que cree la instancia, e impórtalo donde necesites 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"},
)

Las opciones tienen los mismos nombres que en Configuración. Pásalas como argumentos con nombre en snake case, como trust_proxy=False y rate_limit=60, o como un solo dict con los nombres de TypeScript, como Runlight({"store": ..., "trustProxy": False}). Las tablas se crean en la primera petición.

Almacenes

Las tablas de Runlight empiezan por rl_. Cada almacén acepta prefix para usar otro en su lugar, como Stores.sqlite(path, prefix="wp_rl_"), así que dos aplicaciones que comparten una base de datos mantienen cada una sus propias tablas. Un prefijo tiene como máximo 32 letras, dígitos y guiones bajos, y no empieza por un dígito. store.tables() lista las tablas del almacén por nombre, para una aplicación que quiera quitar Runlight.

SQLite

python

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

Guarda el archivo fuera de cualquier carpeta que sirva tu servidor web, en una carpeta en la que la aplicación pueda escribir, ya que SQLite escribe un journal a su lado. La carpeta se crea si no existe.

Postgres

python

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

statement_timeout detiene cualquier sentencia pasados esos milisegundos, y el valor por defecto es 120000. schema guarda las tablas en un schema propio, como en Stores.postgres(url, schema="analytics"). En lugar de una URL puedes pasar una conexión de psycopg que tu aplicación ya tenga, que Runlight usa tal cual y nunca cierra.

MySQL y MariaDB

python

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

Este almacén funciona con MySQL 8.0.20 o posterior y MariaDB 11.4 o posterior. También vale una URL mariadb://. Acepta statement_timeout igual que Postgres, que MySQL aplica a las lecturas y MariaDB a todas las sentencias, como en Configuración.

Stores.url() elige el almacén a partir de un DATABASE_URL que empiece por postgres://, mysql://, mariadb:// o sqlite:.

Django

Añade la app de Runlight y su middleware a tus ajustes, y nombra la instancia en RUNLIGHT. El middleware va el primero, para que ningún otro middleware cambie las peticiones de Runlight ni sus respuestas.

settings.py

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

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

RUNLIGHT = "myproject.analytics.rl"

RUNLIGHT contiene la propia instancia o la ruta con puntos hasta ella, y también vale la ruta a una función que la cree. RUNLIGHT_ROUTES pasa opciones a las rutas con sus nombres de TypeScript, como {"accounts": True}. El middleware sirve el panel y la API bajo /runlight, los enlaces cortos en /go y cada dominio de enlaces que añadas en Ajustes, antes que las URLs propias de Django. Cada dominio de enlaces tiene que estar también en ALLOWED_HOSTS.

Ejecuta la comprobación programada desde cron con el comando de gestión.

bash

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

Flask

Pasa la aplicación y la instancia a init_app, que pone Runlight delante de las vistas propias de la aplicación. Responde bajo /runlight, en /go y en cada dominio de enlaces que añadas en Ajustes, y pasa todo lo demás a tu aplicación.

app.py

from flask import Flask

import runlight.flask
from myproject.analytics import rl

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

Las opciones de las rutas se pasan como argumentos con nombre, como runlight.flask.init_app(app, rl, accounts=True). init_app también añade un comando que ejecuta la comprobación programada, para cron.

bash

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

FastAPI

init_app añade Runlight como middleware delante de las rutas de la aplicación, así que responde bajo /runlight, en /go y en cada dominio de enlaces que añadas en Ajustes. También funciona con aplicaciones Starlette.

main.py

from fastapi import FastAPI

import runlight.fastapi
from myproject.analytics import rl

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

El trabajo propio de Runlight se ejecuta en hilos de trabajo, así que nunca bloquea el event loop. Para la comprobación programada, llama a rl.check() desde un script que ejecute cron, o define CRON_SECRET y llama a la comprobación por HTTP.

Cualquier aplicación WSGI o ASGI

Bottle, Pyramid y cualquier otra aplicación WSGI usan el middleware WSGI.

python

from runlight.wsgi import RunlightMiddleware

application = RunlightMiddleware(application, rl)

Quart, Django bajo ASGI y cualquier otra aplicación ASGI usan el middleware ASGI, que recibe la instancia como runlight=.

python

from runlight.asgi import RunlightMiddleware

application = RunlightMiddleware(application, runlight=rl)

Ambos responden bajo /runlight, en /go y en cada dominio de enlaces, y pasan todo lo demás a tu aplicación. Las opciones de las rutas se pasan como argumentos con nombre, igual que con Flask. Para ejecutar Runlight como una aplicación propia, sin ninguna tuya detrás, usa runlight.wsgi.app(rl) con un servidor WSGI como gunicorn, o runlight.asgi.app(rl) con un servidor ASGI como uvicorn. Las rutas fuera de las de Runlight responden entonces con un 404.

Un enlace corto en /go/<name> sin ningún enlace con ese nombre sigue hacia tu aplicación, así que responde tu propia página 404, sea la de Django, la de Flask, la de FastAPI o la de tu aplicación WSGI o ASGI. runlight.wsgi.app() y runlight.asgi.app() no tienen ninguna aplicación detrás y responden ellos mismos con un 404. En un dominio de enlaces, /go/<name> es un 404 como cualquier otra ruta que no sea uno de los enlaces de ese dominio.

Lo añadas como lo añadas, pon el script en cada página, justo antes de </head>.

html

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

Después define RUNLIGHT_TOKEN como una cadena larga y aleatoria y abre una vez /runlight/?token= seguido de esa cadena para iniciar sesión, como describe Primeros pasos. Con accounts=True y un RUNLIGHT_SECRET, cada persona inicia sesión con su propio correo y contraseña, como en Cuentas.

Agentes de IA

El middleware registra los accesos de los agentes de IA mientras tu aplicación sirve sus páginas, y deja pasar intacta cualquier otra petición. Pasa observe=False a init_app o al middleware para desactivarlo, o define "observe": False en RUNLIGHT_ROUTES en Django. Sin middleware, llama a rl.observe(request) con un runlight.http.Request en cada petición de página.

La comprobación programada

La comprobación programada rota las sales diarias, envía los informes por email pendientes, aplica cuánto tiempo conserva cada sitio sus visitas y construye los resúmenes que mantienen rápidos los rangos largos. Ejecútala cada pocos minutos con python manage.py runlight_check en Django, flask runlight check en Flask o rl.check() desde cualquier script. Cuando nada puede ejecutar un comando con un temporizador, define CRON_SECRET y haz que un programador de tareas llame a /runlight/api/check con él, como muestra Comprobación programada.

El servidor independiente

El paquete incluye el servidor independiente, que ejecuta Runlight en un dominio propio, como stats.example.com, con el panel en la raíz, sitios añadidos desde el panel, cuentas con inicio de sesión y enlaces cortos en cualquier dominio que apuntes hacia él. Instala el paquete y ejecuta el comando runlight.

bash

pip install runlight
runlight

El servidor escucha en el puerto 3000 y guarda sus datos en una carpeta llamada runlight-data dentro del directorio desde el que lo arrancas. En su primer arranque muestra un enlace de configuración con un código de un solo uso. Abre ese enlace y crea tu cuenta, como describe Crea tu cuenta. El servidor ejecuta él mismo la comprobación programada cada cinco minutos, y descarga los datos de ubicación del mes igual que el servidor de Node. Ponlo detrás de un proxy que añada HTTPS, como en Publícalo en internet.

Ajustes

El servidor lee sus ajustes de variables de entorno.

Variable Qué hace
PORT El puerto en el que escucha. Por defecto, 3000.
HOST La dirección en la que escucha. Por defecto, 0.0.0.0.
DATA_DIR La carpeta para el archivo SQLite, el secreto y los datos de ubicación. Por defecto, ./runlight-data.
DATABASE_URL Una dirección postgres:// guarda los datos en Postgres en lugar de SQLite, y una dirección mysql:// o mariadb:// los guarda en MySQL o MariaDB.
RUNLIGHT_SECRET La clave que firma los inicios de sesión y cifra las claves guardadas. Sin ella, el servidor crea una y la guarda en DATA_DIR, legible solo por el usuario que lo ejecuta.
RUNLIGHT_URL La dirección pública del servidor, como https://stats.example.com, que nunca puede convertirse en dominio de enlaces. Los correos de invitación y de informes enlazan a ella.
RUNLIGHT_TOKEN Un token que los scripts pueden enviar como bearer, además de los tokens de API creados en el panel.
RUNLIGHT_OBSERVE_KEY Una sola clave para los informes de agentes de IA de todos los sitios. La clave de agentes propia de cada sitio, que aparece en Ajustes, Instalación, es la mejor opción.
TRUST_PROXY Defínela como false cuando no haya ningún proxy delante del servidor, o como x-real-ip o cf-connecting-ip cuando esa cabecera lleve la dirección del visitante.
RUNLIGHT_GEO De dónde salen las ubicaciones cuando ninguna cabecera las da, como en Ubicaciones. Por defecto es city, y puede ser country, off o la ruta a un archivo MMDB.
CRON_SECRET Permite que un programador de tareas ejecute la comprobación en POST /api/check.

Comandos

runlight check ejecuta la comprobación programada una vez y obtiene los datos de ubicación del mes. runlight migrate crea o actualiza las tablas al momento. Las tablas también se actualizan solas cuando arranca el servidor, así que para actualizar basta con pip install --upgrade runlight y un reinicio. Haz copia de seguridad de la carpeta de datos, ya que las claves guardadas no se pueden leer sin el secreto que contiene.

Contraseñas olvidadas

Ejecuta el comando password donde se ejecuta el servidor para darle a una cuenta una contraseña nueva, que el comando muestra. También desactiva el inicio de sesión en dos pasos de esa cuenta, y crea la cuenta si no existe, como propietario en un servidor que aún no tiene a nadie y como administrador en los demás casos.

bash

runlight password someone@example.com

Agentes de IA desde un log

Los agentes de IA como GPTBot y ClaudeBot acceden a las páginas sin ejecutar JavaScript, así que la etiqueta de script nunca los ve. Una aplicación Python con Runlight dentro los cuenta donde se sirve la página, como muestra Agentes de IA. Para cualquier otro sitio que alojes tú, el log de acceso del servidor web los tiene, y el comando agents lo lee.

bash

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

La clave es la clave de agentes del sitio, que aparece en Runlight en Ajustes, Instalación, y solo puede informar de accesos a ese sitio. Cuando falta --to o --key, el comando lee RUNLIGHT_URL o RUNLIGHT_OBSERVE_KEY del entorno. El comando funciona igual que en el servidor independiente, con --follow para seguir en marcha y --state para continuar donde se quedó la última ejecución. Solo salen de la máquina los accesos correctos a páginas de agentes de IA conocidos, y las direcciones de los visitantes se quedan donde están.

En qué se diferencia de la biblioteca de TypeScript

El paquete de Python pasa las pruebas de conformidad que se exigen a la biblioteca de TypeScript, en SQLite, Postgres, MySQL y MariaDB. Las diferencias vienen de cómo se ejecuta Python.

  • El trabajo de Runlight es síncrono y se ejecuta en hilos. El middleware ASGI pasa cada una de sus peticiones a un hilo de trabajo, así que el event loop queda libre para el resto de tu aplicación.
  • SQLite mantiene una sola conexión, que todos los hilos comparten con un bloqueo. Postgres y MySQL abren una conexión por hilo, así que un servidor con dieciséis hilos mantiene hasta dieciséis conexiones.
  • Lo que la biblioteca de TypeScript hace en segundo plano, como borrar visitas después de elegir una retención más corta, se ejecuta una vez enviada la respuesta.
  • El límite de peticiones del tracker y las listas en caché de dominios de enlaces y de iconos de sitios se guardan en memoria, así que cada proceso tiene las suyas. Un servidor con varios procesos de trabajo permite a cada dirección el límite una vez en cada proceso. Los límites de contraseñas incorrectas se guardan en la base de datos, así que todos los procesos los comparten.
  • El hash de las contraseñas se calcula con scrypt a través de hashlib de Python. Los hashes coinciden con los de la biblioteca de TypeScript, así que las cuentas se mantienen.