LogAlert is a local log monitoring Plugin for the xyOps Workflow Automation System. Run it on a schedule, point it at one or more files or glob patterns, and tell it which text or regular expressions matter. LogAlert remembers how far it has read in every file and only searches newly appended lines on later runs.
When LogAlert finds something important, it adds a user-selected xyOps tag to the job and can attach searchable evidence. You can then configure normal xyOps completion actions for that tag, including email, web hooks, channels, tickets, follow-up events, custom Action Plugins, and more.
This makes LogAlert useful for applications which report their most important state through text files, especially legacy services, custom daemons, database servers, batch processors, edge systems, and air-gapped infrastructure.
- Pure vanilla Node.js, compiled into a single inline xyOps script.
- Uses the Node.js runtime, xyOps SDK, and pixl-tools library bundled with xySat.
- Runs as a normal xyOps Event Plugin on the target satellite server.
- Watches individual files or glob patterns, including directory and recursive globs.
- Uses the picomatch glob engine, including
*,?, recursive**, character classes, braces, and extglobs. - Searches only newly appended bytes after the first run.
- Supports literal text and JavaScript regular expressions.
- Supports multiple paths and multiple match patterns in each monitor.
- Supports case-sensitive and case-insensitive matching.
- Handles normal appends, file replacement, rename rotation, and copy/truncate rotation.
- Preserves incomplete trailing lines between runs.
- Uses device and inode identity where available, with content fingerprint protection.
- Stores durable cursor state locally on each satellite server.
- Adds a selected xyOps tag only when matches are found.
- Attaches matching lines with context, the scanned delta, or the whole log file.
- Produces structured output data, a match summary table, and a detailed Markdown report.
- Writes readable glob, file, match, and completion activity to the job log.
- Works especially well with xyOps Quiet Mode and Multiplex workflows.
- xyOps v1.0.93 or newer.
- A normal xySat installation on each target server.
- Read permission for every configured log path.
- Write permission for the local LogAlert state directory.
No separate Node.js, npm, npx, Git, Docker, package installation, or external service is required on target servers. xySat launches the inline Plugin with its own bundled Node.js runtime.
LogAlert makes no package downloads or outbound network requests during a job.
Create a new xyOps tag for LogAlert matches and give it a clear title, such as Log Alert. After saving the tag, copy its automatically generated ID to the clipboard for use when configuring alert conditions.
Note
This should be a dedicated conditional tag. Do not assign the tag directly to the LogAlert event. The Plugin must be the component that adds it during job runs.
Find LogAlert in the xyOps Marketplace and install it. The Marketplace export contains the complete compiled Plugin as an inline script. Its launch command is simply node, which xySat resolves to its own bundled runtime. Installation does not download an npm package onto the target server.
Create an event using the LogAlert Plugin and target the server which owns the log files. Select your dedicated tag in the Alert Tag menu, then choose the result you want matching jobs to have in Alert Code. Success preserves the original successful-job behavior, while Error, Warning, and Critical make an alert stand out accordingly in xyOps history, emails, actions, and workflow routing.
Edit the Monitors JSON to describe the files and patterns you want to watch. Here is a small example:
[
{
"id": "my-app-errors",
"name": "My App Errors",
"paths": [ "/var/log/myapp/*.log" ],
"matches": [ "Fatal Error" ],
"regexp": false,
"case_sensitive": true
}
]This example will alert if the phrase Fatal Error appears in any log files in the /var/log/myapp/ directory.
Add a normal xyOps schedule trigger to the event. Every minute is a good starting cadence for many applications, but you can use any frequency that suits the log volume and desired response time.
LogAlert is intentionally a short-lived scheduled job, not a background daemon. Every run discovers files, reads the delta byte ranges, saves its cursors, reports completion, and exits.
Add the scheduler's Quiet modifier and enable both Invisible and Ephemeral modes.
Most runs will find nothing, produce no output file, and disappear automatically. When a match is found, LogAlert explicitly disables ephemeral deletion before adding the alert tag. The interesting job remains visible with its tag, summary, activity history, and any searchable evidence, even when No Attachment is selected.
Add one or more completion actions to the event and choose the dedicated LogAlert tag as the action condition. The internal condition is tag:TAGID.
The selected Alert Code controls the result only when a match is found. No-match runs always complete successfully with numeric code 0. Matching runs can use numeric 0 for Success, numeric 1 for Error, or xyOps' special string codes warning and critical. The dedicated tag is still added in every alert state, independent of the selected code.
The Monitors parameter is a JSON array. Each object describes one logical monitor with its own paths, patterns, and optional evidence settings.
| Property | Required | Description |
|---|---|---|
id |
Recommended | Stable unique ID used to namespace cursor state. Defaults to name. Do not change this casually after deployment. |
name |
No | Friendly monitor name shown in job results. Defaults to Monitor N. |
paths |
Yes | Array of file paths and/or glob patterns combined into one monitor. Absolute paths are strongly recommended. |
matches |
Yes | Array of literal strings or regular expressions. A line matches when any pattern matches. |
regexp |
No | Interpret patterns as JavaScript regular expressions. Defaults to false, which performs escaped literal matching. |
case_sensitive |
No | Enable case-sensitive matching. Defaults to true. |
excludes |
No | Array of exclusion glob patterns. A basename pattern such as *.gz applies to filenames. |
attachment |
No | Override the event attachment mode for this monitor: context, delta, whole, or none. |
context_lines |
No | Override the event context line count for this monitor's Markdown report and Match Context attachment. |
The paths, matches, and optional excludes properties are always arrays, even when they contain only one item. Monitor patterns are combined with OR behavior. For example, "matches": ["ERROR", "PANIC"] finds a line containing either literal string when regexp is false.
[
{
"id": "postgres",
"name": "PostgreSQL",
"paths": [
"/var/log/postgresql/postgresql.log"
],
"matches": [
"PANIC",
"database system is shutting down"
]
}
]* matches within one directory level and ? matches one non-separator character.
[
{
"id": "web-errors",
"name": "Web Server Errors",
"paths": [
"/var/log/nginx/*error*.log"
],
"matches": [
"upstream timed out",
"connect() failed"
],
"case_sensitive": false
}
]Use a trailing * glob to watch the files immediately inside a directory:
[
{
"id": "service-directory",
"name": "Service Log Directory",
"paths": [
"/opt/services/logs/*"
],
"matches": [
"Fatal Error"
]
}
]Use a ** glob to include every descendant directory:
[
{
"id": "all-service-logs",
"name": "All Service Logs",
"paths": [
"/opt/services/logs/**/*"
],
"matches": [
"Fatal Error",
"Unhandled Exception"
],
"excludes": [
"*.gz"
]
}
]** matches across any number of directory levels:
[
{
"id": "recursive-app-logs",
"name": "Recursive Application Logs",
"paths": [
"/srv/apps/**/*.log"
],
"matches": [
"FATAL"
]
}
]In addition to *, ?, and recursive **, patterns may use character classes, brace alternatives, and extglobs. Glob traversal follows symbolic links and skips dotfiles.
A literal directory path matches the directory itself, following standard glob semantics. Since LogAlert only scans regular files, use a wildcard such as /var/log/myapp/* when you want the files inside a directory.
[
{
"id": "database-cluster",
"name": "Database Cluster",
"paths": [
"/var/log/postgresql/*.log",
"/var/log/pgbouncer/pgbouncer.log",
"/opt/database/custom-logs/*"
],
"matches": [
"PANIC",
"out of memory",
"too many connections"
],
"case_sensitive": false,
"excludes": [
"*.gz",
"*.zip"
]
}
]Set regexp to true to use JavaScript regular-expression syntax:
[
{
"id": "http-5xx",
"name": "HTTP 5xx Responses",
"paths": [
"/var/log/myapp/access-*.log"
],
"matches": [
"\\s5\\d\\d\\s"
],
"regexp": true,
"case_sensitive": false
}
]Remember that JSON requires backslashes inside strings to be escaped. So the JSON text "\\d+" becomes the regular expression \d+.
Regular expressions are administrator-supplied executable patterns. Avoid expressions with catastrophic backtracking, especially when scanning long lines.
Each monitor has independent cursor state and can override evidence settings:
[
{
"id": "app-fatal",
"name": "Application Fatal Errors",
"paths": [
"/var/log/myapp/*.log"
],
"matches": [
"Fatal Error"
],
"attachment": "context",
"context_lines": 5
},
{
"id": "kernel-oom",
"name": "Kernel OOM Killer",
"paths": [
"/var/log/messages",
"/var/log/syslog"
],
"matches": [
"Out of memory: Killed process"
],
"case_sensitive": false,
"attachment": "delta"
}
]If any monitor matches, LogAlert adds the selected Alert Tag once. All matches from the run are consolidated into one job result.
LogAlert supports four evidence modes:
| Mode | Value | Behavior |
|---|---|---|
| Match Context | context |
Attach a text report containing matching lines plus configurable surrounding lines. This is the recommended default. |
| Scanned Delta | delta |
Attach the raw byte range scanned during this run. |
| Whole Log File | whole |
Attach a copy of the source log as it appeared when scanning began. |
| No Attachment | none |
Add the tag, Markdown match report, and structured result data without uploading a file. |
The Max Attachment Size setting protects xyOps storage and job transfer time. It accepts friendly values such as 512 KB, 10 MB, or 1 GB. Markdown evidence and context reports are trimmed to this limit. Delta and whole-file copies are clipped to the first configured number of bytes. Set the limit to 0 only when you intentionally want unlimited evidence.
Match Context is usually the best operational choice. It preserves the relevant evidence, keeps matching jobs searchable, and avoids uploading unrelated secrets or gigabytes of historical data.
Output filenames contain the source filename without its extension, followed by a short random ID and .txt. For example, application.log may produce application-mt6gsmvvfesqygvp.txt. The random ID keeps attachments distinct when several directories contain logs with the same basename.
LogAlert stores a byte cursor for every discovered file. State is local to the satellite server because the logs themselves are local to that server.
State lives here relative to the xySat installation:
state/
The event's configured UID must be able to create and update files there.
State files are namespaced by the xyOps Event ID and, for ad-hoc workflow jobs, the Workflow Node ID. Two different LogAlert events can safely watch the same physical file with independent patterns and cursors.
Each cursor includes:
- Last processed byte offset.
- Last observed file size and modification time.
- Device and inode identity when the operating system provides them.
- A small beginning-of-file fingerprint.
- Any incomplete trailing line.
- Last-seen time for state expiration.
State is written atomically after all scans and evidence files are staged. A local lock prevents overlapping jobs from modifying the same cursor namespace concurrently.
The default Initial Position is End of Existing Files. On the first successful run, LogAlert records the current end of every discovered file without searching historical content. Only later appends are considered.
Choose Beginning of Existing Files when you intentionally want to search existing content. The Max Scan Size limit still applies, so a large historical file can be processed safely over several scheduled runs.
After a monitor is initialized, any newly discovered file starts at byte zero. This includes a replacement file created by log rotation.
For the best rotation coverage, make the glob include both the active and rotated files. For example:
{
"paths": [
"/var/log/myapp/application.log*"
]
}When application.log is renamed to application.log.1, LogAlert recognizes the old file by device and inode and continues at its existing cursor. A newly created application.log receives a new identity and starts at byte zero.
If the rotated file moves completely outside the configured paths between polling runs, LogAlert may not be able to read bytes written immediately before rotation. Include rotated filenames in the glob whenever complete coverage matters.
If the same file becomes shorter than its cursor, LogAlert treats it as truncated and starts again at byte zero. A changed beginning-of-file fingerprint provides additional replacement detection.
As with other polling readers, a copy/truncate strategy can theoretically lose bytes written between the copy and truncate operations. Rename-and-create rotation is preferable when you control the application or rotation policy.
LogAlert is line-oriented. Bytes after the final newline are retained in cursor state and joined with bytes from the next run. The line is searched once it receives a newline. This avoids duplicate alerts while an application is still writing a line.
Many LogAlert deployments should run the same rules once on every server in a xyOps group. A workflow with a Multiplex Controller is the recommended way to do this.
- Create a new xyOps workflow.
- Add a schedule trigger to the workflow, such as every minute.
- Add a Multiplex Controller.
- Add a Job node and select the LogAlert Plugin.
- Configure the Job node with your monitor JSON, dedicated Alert Tag, and target server group.
- Connect the schedule trigger to the Multiplex Controller.
- Connect the Multiplex Controller's single output to the LogAlert Job node.
- Save and enable the workflow.
The Multiplex Controller expands the LogAlert Job node's target group into concrete enabled servers, applies server alert filters, and launches one LogAlert sub-job on each server. Each sub-job reads that server's local files and updates that server's local cursor state.
Multiplex also has an optional stagger value. Use it to spread job starts over a few seconds when the group is large or the watched storage is shared. The controller supports a continue percentage when later workflow steps should wait for a desired percentage of the server jobs to succeed.
Do not apply a Max Concurrent Jobs limit of 1 to the multiplexed LogAlert node. That would serialize or reject the per-server jobs which Multiplex intentionally launches together. If overlapping workflow runs are possible, use a suitable concurrency and queue policy based on the server group size and expected scan duration.
Add the Quiet scheduler modifier to the workflow and enable Invisible and Ephemeral. xyOps automatically passes both settings down to workflow sub-jobs.
This produces the desired fleet behavior:
- Servers with no matching lines produce no alert content and their sub-jobs disappear.
- Servers with matches explicitly disable ephemeral deletion for their sub-jobs, whether or not evidence is attached.
- The matching jobs remain visible and searchable with their server identity and Alert Tag.
- Tags from sub-jobs bubble up to the parent workflow job.
You can attach tag-conditioned actions directly to the LogAlert Job node when you want one response per matching server. You can also use the bubbled tag on the parent workflow when you want one aggregate response after the fleet scan completes.
The Alert Code also participates in normal workflow routing. If you select Error, Warning, or Critical, matching sub-jobs are not counted as successful by Multiplex and can follow the corresponding error, warning, or critical output condition. Keep Success selected when the tag alone should represent the alert and the Multiplex success threshold should remain unaffected.
| Parameter | Default | Description |
|---|---|---|
| Monitors | Example JSON array | Files and match rules to run. |
| Alert Tag | None | Required dedicated job tag added when matches are found. |
| Alert Code | Success | Result assigned to matching jobs: Success (0), Error (1), Warning (warning), or Critical (critical). No-match jobs always use numeric 0. |
| Attachment Mode | Match Context | Evidence uploaded for each matching source file. |
| Context Lines | 0 |
Lines before and after a match in the Markdown report and context attachments. Increase this when surrounding log lines will help explain an alert. |
| Max Attachment Size | 10 MB |
Markdown evidence and per-attachment size limit. Accepts values such as 512 KB, 10 MB, or 1 GB. 0 is unlimited. |
| Initial Position | End | First-run cursor behavior for files which already exist. |
| Max Scan Size | 25 MB |
Maximum new data scanned per file per run. Friendly size units are accepted, and 0 is unlimited. |
| Max Files | 1000 |
Maximum files discovered by one monitor. |
| Max Evidence Matches | 1000 |
Maximum matches represented per file in the Markdown report and context attachments. |
| Max Line Size | 1 MB |
Memory safety limit for one unfinished line. Accepts friendly size units. |
| Fail on Missing Files | Disabled | Fail rather than warn when a monitor discovers no files. |
| State Retention | 30 days |
Age at which unseen file cursors are forgotten. Accepts values such as 12 hours, 30 days, or 2 weeks. |
| Verbose Output | Disabled | Include extra scan, cursor, byte range, and state details in the job log. |
Size values use binary units, so 1 KB is 1,024 bytes and 1 MB is 1,024 KB. Bare size numbers are interpreted as bytes. Duration values support seconds, minutes, hours, days, and weeks. Bare duration numbers are interpreted as seconds.
The true match total is still reported when the evidence match limit is reached. When the scan byte limit is reached, LogAlert saves the partial cursor and continues from that exact point during the next run.
Every completed run emits data.logalert with fields including:
{
"matched": true,
"alertCode": "warning",
"matchCount": 2,
"matchedFiles": 1,
"filesScanned": 4,
"bytesScanned": 1837,
"monitorCount": 2,
"warningCount": 0,
"warnings": [],
"elapsedMs": 19,
"server": "server-id",
"event": "event-id"
}When at least one match is found, the job also receives:
- The selected job tag through
push.tags. - Zero or more evidence files, depending on Attachment Mode.
- A LogAlert Matches table listing monitor, source path, count, and byte range.
- A LogAlert Report in Markdown, grouped by matching source file. Each monitor subsection includes its totals and a fenced text block containing every captured matching row. Matching rows use a
>prefix, while optional context rows use|.
The Markdown report is generated for every alert, including No Attachment mode. If Max Evidence Matches or Max Attachment Size prevents all matching rows from being included, the report preserves the true count and clearly summarizes how many rows are shown. This gives tag-conditioned emails useful alert details without requiring the recipient to download an attachment.
LogAlert also writes a readable activity log to STDOUT. Normal output includes each glob scan, each file processed, any match discoveries, and a final summary. Enable Verbose Output to add state-file, cursor-reset, and byte-range details.
No-match jobs always complete with numeric code 0. Matching jobs use the configured Alert Code after their tag, evidence, table, and Markdown report have been published. Configuration, filesystem, state, and oversized-line failures continue to use the custom logalert error code with a friendly description.
Missing files are warnings by default. This is convenient when an application creates its log lazily or a glob only matches during certain operations. The monitor is still considered initialized, so a file which appears later starts at byte zero.
Enable Fail on Missing Files when absence indicates a broken installation or an unexpected service state.
Permission errors always fail the job. Make sure the Plugin UID can:
- Traverse every parent directory in a watched path.
- Read each discovered log file.
- Create and update xySat's standard local
state/directory. - Write evidence files in the xyOps job working directory.
Log files often require membership in a system group such as adm. Configure the Plugin credentials appropriately instead of running it with broader privileges than necessary.
LogAlert intentionally reads local files selected by an administrator. Treat access to its event parameters as privileged. A user who can edit paths may be able to read other files available to the Plugin UID and upload matching content into xyOps.
Evidence files become part of xyOps job history. They may be searchable, downloadable, forwarded to later workflow steps, and included in configured actions. Logs can contain session tokens, customer data, database queries, email addresses, or other sensitive information. Prefer Match Context, use narrow patterns, set conservative limits, and follow your organization's retention policy.
LogAlert does not collect or transmit telemetry, analytics, usage metrics, or personal data to PixlCore or any third party. It makes no outbound network requests during a job.
The Plugin performs only these data operations:
- Reads the local file paths configured by the xyOps administrator.
- Stores local cursor metadata, small content fingerprints, and incomplete trailing-line bytes on the satellite server.
- Returns match summaries, the selected tag, and optional evidence files to the user's own xyOps installation.
The target servers do not need npm, but contributors use it locally to install the build and test tools. After cloning the repository, install the development dependencies and run the source integration suite:
npm install
npm testThe source tests run directly from index.js, so they work before rebuilding the inline payload. They use temporary directories and exercise first-run behavior, appends, partial lines, standard and recursive globs, exclusions, regular expressions, rename rotation, truncation, tags, Markdown reports, STDOUT logging, and evidence attachments.
Run the local build command whenever a source file changes:
npm run buildThe build script uses esbuild to combine index.js and all local modules into one compact CommonJS program. It intentionally leaves require("@pixlcore/xyops-sdk") and require("pixl-tools") external, because xySat supplies both packages at runtime. The finished script is inserted directly into the script property in xyops.json, and the launch command is set to node.
To execute the exact compiled payload from xyops.json, while simulating xySat's parent-directory module resolution, run:
npm run test:compiledTo run both source and compiled-payload verification in one command, use:
npm run verifyYou can also invoke the Plugin directly by piping an xyOps-style job document into Node.js:
node index.js <<'JSON'
{
"xy": 1,
"type": "event",
"id": "jtest",
"event": "etest",
"server": "stest",
"cwd": "/tmp/logalert-job",
"temp_dir": "/tmp/xyops-satellite/temp",
"params": {
"alert_tag": "log-alert",
"initial_position": "beginning",
"attachment_mode": "context",
"monitors": [
{
"id": "system-log",
"name": "System Log",
"paths": [
"/var/log/system.log"
],
"matches": [
"error"
],
"case_sensitive": false
}
]
}
}
JSONThe Plugin writes readable scan messages and compact xyOps Wire Protocol updates to STDOUT, followed by a final completion document.
For maintainers preparing a release:
- Update the version in
package.json. - Run
npm run buildto refresh the inline script inxyops.json. - Run
npm run verifyto test both the source and compiled payload. - Review and commit the generated
xyops.jsonchange. - Confirm
logo.pngremains square, transparent, and at least 128 by 128 pixels. - Create and push the matching Git tag, such as
v1.0.0. - Add the release to the xyOps Marketplace metadata repository.
Copyright (c) 2026 PixlCore LLC.
Released under the MIT License.
