Drupal Signals Reference
The Logystera Agent for Drupal emits raw signals — single-occurrence observations of security and operational events. No aggregation, deduplication, or alerting happens in the module itself. All intelligence lives in the Logystera processor.
Bounded signal volume. Per-request caps prevent runaway emission. Each signal type has a fixed maximum per request, with a global cap of 200 events. The module never becomes an unbounded source of data regardless of what happens on the site.
Local-first buffering. Signals are written to a local JSONL file (private://logystera/buffer-current.jsonl) during the request. Network I/O never blocks the request lifecycle. Buffered signals are dispatched to the gateway on cron or via manual flush.
!!! info "Glossary" Signal: A single observed event (login, config change, etc.) captured as a JSON object and buffered locally.
**Buffer:** JSONL files in `private://logystera/` that hold signals until they are dispatched to the gateway. Rotates at 5 MB per file, hard cap at 50 MB total.
**Dispatch:** The act of sending buffered signals to the Logystera gateway. Triggered by Drupal cron or the manual "Flush Now" button.
Event schema
Every signal buffered by the module has this structure:
{
"event_type": "auth.login",
"created_at": 1774211679,
"timestamp": 1774211679,
"event_id": "9ad113c7-0ff8-4dc6-bb68-1b836a90ea80",
"labels": { ... }
}
| Field | Type | Description |
|---|---|---|
event_type |
string |
Signal type identifier (e.g. config.save, auth.login). |
created_at |
int |
Unix timestamp when the signal was captured. |
timestamp |
int |
Same as created_at. Used for gateway compatibility. |
event_id |
string |
UUID v4. Unique identifier for each signal. |
labels |
object |
Signal-specific payload. Structure varies by type. |
Privacy: UID hashing
All user IDs in labels are HMAC-SHA256 hashed using the entity secret (or a site-derived key as fallback). Raw Drupal user IDs are never transmitted.
Standard Signals
These are enabled by default when the module is active.
config.save
Emitted when any Drupal configuration object is saved (created or updated). The module's own config changes are excluded to prevent recursion.
{
"config_name": "system.site",
"category": "system",
"change_type": "update",
"actor_uid": "a1b2c3d4..."
}
| Field | Description |
|---|---|
config_name |
Full config object name (e.g. system.site, user.settings). |
category |
Semantic category from CategoryMapper (e.g. system, field, views). |
change_type |
create or update. |
actor_uid |
HMAC-hashed UID of the user who made the change. |
Config key: signals.config_save (default: ON)
Rate limit: 50 per request.
config.delete
Emitted when a configuration object is deleted.
{
"config_name": "field.field.node.article.body",
"category": "field",
"change_type": "delete",
"actor_uid": "a1b2c3d4..."
}
Config key: signals.config_delete (default: ON)
Rate limit: 20 per request.
config.import
Emitted when a configuration import/sync is executed.
{
"actor_uid": "a1b2c3d4...",
"change_type": "import"
}
Config key: signals.config_import (default: ON)
Rate limit: 5 per request.
auth.login
Emitted on successful user login.
{
"uid": "a1b2c3d4...",
"ip": "192.168.1.100",
"roles": ["authenticated", "administrator"]
}
| Field | Description |
|---|---|
uid |
HMAC-hashed user ID. |
ip |
Client IP address. |
roles |
Array of Drupal role machine names. |
Config key: signals.auth_login (default: ON)
Rate limit: 1 per request.
auth.login_failed
Emitted when a login attempt fails (wrong credentials, blocked account).
{
"username": "admin",
"ip": "192.168.1.100"
}
| Field | Description |
|---|---|
username |
The username that was attempted (not hashed — it may not correspond to a real account). |
ip |
Client IP address. |
Config key: signals.auth_login_failed (default: ON)
Rate limit: 50 per request.
auth.logout
Emitted when a user logs out.
{
"uid": "a1b2c3d4..."
}
Config key: signals.auth_logout (default: ON)
Rate limit: 1 per request.
user.created
Emitted when a new user account is created.
{
"uid": "a1b2c3d4...",
"roles": ["authenticated"],
"actor_uid": "e5f6a7b8..."
}
Config key: signals.user_created (default: ON)
Rate limit: 10 per request.
user.role_changed
Emitted when a user's roles are modified.
{
"uid": "a1b2c3d4...",
"roles_before": ["authenticated"],
"roles_after": ["authenticated", "editor"],
"roles_added": ["editor"],
"roles_removed": [],
"actor_uid": "e5f6a7b8..."
}
Config key: signals.user_updated (default: ON)
Rate limit: 20 per request.
user.blocked
Emitted when a user account is blocked (status changed from active to blocked).
{
"uid": "a1b2c3d4...",
"actor_uid": "e5f6a7b8..."
}
Config key: signals.user_updated (default: ON)
Rate limit: 10 per request.
user.deleted
Emitted when a user account is deleted.
{
"uid": "a1b2c3d4...",
"actor_uid": "e5f6a7b8..."
}
Config key: signals.user_deleted (default: ON)
Rate limit: 10 per request.
module.install
Emitted when a Drupal module is installed.
{
"module": "views_ui",
"is_syncing": false,
"actor_uid": "a1b2c3d4..."
}
| Field | Description |
|---|---|
module |
Machine name of the installed module. |
is_syncing |
true if triggered by config sync rather than manual install. |
Config key: signals.module_install (default: ON)
Rate limit: 20 per request.
module.uninstall
Emitted when a Drupal module is uninstalled.
{
"module": "views_ui",
"is_syncing": false,
"actor_uid": "a1b2c3d4..."
}
Config key: signals.module_uninstall (default: ON)
Rate limit: 20 per request.
http.request
Emitted once per HTTP request at kernel terminate (after the response is sent). Only fires for requests that reach PHP — pages served from Drupal's Internal Page Cache do not trigger this signal.
{
"method": "GET",
"path": "/admin/content",
"status_code": 200,
"ip": "192.168.1.100",
"is_admin": true,
"is_ajax": false,
"actor_uid": "a1b2c3d4...",
"ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)..."
}
| Field | Description |
|---|---|
method |
HTTP method (GET, POST, etc.). |
path |
Request path (e.g. /admin/content, /node/42). |
status_code |
HTTP response status code. |
ip |
Client IP address. |
is_admin |
true if the path starts with /admin/. |
is_ajax |
true if the request is an XMLHttpRequest. |
ua |
User agent string (truncated to 200 chars). |
!!! note Cached anonymous pages bypass PHP entirely and won't generate signals. This is by design — you'll see signals for admin pages, authenticated users, form submissions, and cache misses.
Config key: signals.page_visit (default: ON)
Rate limit: 1 per request.
Advanced Signals
These are disabled by default and must be enabled in the module settings (/admin/config/system/logystera).
content.moderation
Emitted when an entity's content moderation state changes (requires Content Moderation module).
{
"entity_type": "node",
"entity_id": "42",
"bundle": "article",
"label": "My Article Title",
"old_state": "draft",
"new_state": "published",
"actor_uid": "a1b2c3d4..."
}
| Field | Description |
|---|---|
entity_type |
Entity type (e.g. node, taxonomy_term). |
entity_id |
Entity ID. |
bundle |
Content type / bundle (e.g. article, page). |
label |
Entity label/title. |
old_state |
Previous moderation state (new for new entities). |
new_state |
New moderation state (e.g. draft, review, published). |
Config key: advanced_signals.content_moderation (default: OFF)
Rate limit: 20 per request.
views.change
Emitted when a Views configuration is saved (created, updated, or deleted). This is a specialized subset of config.save that focuses on Views changes.
{
"config_name": "views.view.content",
"view_id": "content",
"change_type": "update",
"actor_uid": "a1b2c3d4..."
}
Config key: advanced_signals.views_change (default: OFF)
Rate limit: 20 per request.
api.access
Emitted on every JSON:API or REST API request (paths starting with /jsonapi/ or /api/). Captured at kernel terminate so it doesn't block the response.
{
"method": "GET",
"path": "/jsonapi/node/article",
"status_code": 200,
"ip": "192.168.1.100",
"actor_uid": "a1b2c3d4..."
}
Config key: advanced_signals.api_access (default: OFF)
Rate limit: 50 per request.
cache.flush
Emitted when Drupal's cache is rebuilt/flushed (via drush cr, admin UI, or programmatic cache flush).
{
"actor_uid": "a1b2c3d4..."
}
Config key: advanced_signals.cache_flush (default: OFF)
Rate limit: 5 per request.
db.slow_query
Emitted when a database query exceeds 1000ms. Requires database logging to be active (e.g. Devel module or custom logging setup).
{
"query": "SELECT n.nid FROM {node} n WHERE ...",
"time_ms": 1523.4,
"caller": "Drupal\\node\\NodeStorage::loadMultiple"
}
| Field | Description |
|---|---|
query |
SQL query (truncated to 500 characters). |
time_ms |
Query execution time in milliseconds. |
caller |
PHP function that executed the query. |
Config key: advanced_signals.slow_query (default: OFF)
Rate limit: 20 per request.
!!! warning Database logging adds overhead. Only enable this signal when actively investigating performance issues, or use Devel module's query log in development.
Data privacy
| Field | Treatment |
|---|---|
| User IDs | HMAC-SHA256 hashed using entity secret. Raw UID never transmitted. |
| Usernames | Transmitted as-is only for failed login attempts (may not be real accounts). |
| IP addresses | Transmitted as-is. |
| SQL queries | Truncated to 500 chars for slow_query signal only. |
| Config values | Never transmitted. Only config object names are captured. |
Global rate limits
| Scope | Limit |
|---|---|
| Total events per request | 200 (hard cap) |
| Buffer file rotation | 5 MB per file |
| Total buffer cap | 50 MB (new signals dropped when full) |
Buffer full policy
When the buffer reaches 50 MB, new signals are dropped (DROP_NEWEST policy). Old signals are preserved because they have higher forensic value — the earliest events in an incident are the most diagnostic.
Dispatch
| Trigger | Description |
|---|---|
| Drupal cron | hook_cron() calls Dispatcher::flush() every cron run. |
| Manual flush | Admin UI button at /admin/config/system/logystera/status. |
| On connect | Buffered events are flushed immediately after OAuth connection completes. |
The dispatcher chunks events into batches (default: 100 per batch), signs each with HMAC, and sends to the gateway. Failed dispatches trigger exponential backoff (60s base, 30min cap). After 5 consecutive auth failures (401/403), dispatch is suspended until credentials are updated.