SentryEdge is an open-source logging tool that demonstrates how easy and fast it is to build and deploy modern solutions with Cloudflare. Thanks to Cloudflare Workers and the D1 database, you can create your own logging and monitoring services in minutes instead of days or months, without complex infrastructure or expensive SaaS providers. Cloudflare makes "vibe coding" productive: develop, deploy, and run your own services directly at the edge, flexible, cost-effective, and with full data ownership. SentryEdge is an example of how you can use Cloudflare to deliver professional tools for security, observability, and more in no time. This project uses Cloudflare Workers and D1 as a database for storing and analyzing logs.
SentryEdge is also an ideal foundation for extending with Cloudflare Zero Trust, Cloudflare Pub/Sub, or even Cloudflare AutoRAG for advanced anomaly detection and secure, scalable event-driven architectures.
- SentryEdge Worker
Node.js (v18 or newer) available at nodejs.org npm available at npmjs.com Wrangler CLI available at Cloudflare Workers documentation
To install and deploy SentryEdge, simply run:
./deploy.shThis script will handle all setup, including installing dependencies, creating the D1 database, updating configuration, building the UI, and deploying the Worker. No manual steps or extra scripts are required.
The deploy.sh script automates the entire deployment process for your SentryEdge project. Here’s what it does, step by step:
-
Ensures Wrangler CLI is installed
- Checks if the Cloudflare Wrangler CLI is available.
- If not, installs it globally using npm.
-
Ensures you are logged in to Cloudflare
- Checks if you are authenticated with Wrangler.
- If not, runs
wrangler loginto prompt you to log in.
-
Ensures the D1 database exists
- Checks if a D1 database named
sentryedgeexists in your Cloudflare account. - If not, creates it using
wrangler d1 create sentryedge.
- Checks if a D1 database named
-
Extracts and updates the D1
database_id- Extracts the
database_idfor thesentryedgedatabase (whether newly created or already existing) using a robust, portable shell command. - Updates the
database_idfield inworker/wrangler.tomlso your Worker is always connected to the correct database.
- Extracts the
-
Applies D1 migrations
- Runs
wrangler d1 migrations apply sentryedgeto ensure your database schema is up to date.
- Runs
-
Builds the Next.js UI
- Changes to the
uidirectory. - Installs dependencies (
npm install). - Builds the static UI (
npm run build).
- Changes to the
-
Copies static UI files to the Worker
- Copies the built static files from
ui/out/toworker/public/, ensuring the Worker serves the latest UI.
- Copies the built static files from
-
Installs Worker dependencies
- Changes to the
workerdirectory. - Installs dependencies (
npm install).
- Changes to the
-
Deploys the Worker
- Runs
wrangler deployto deploy your Worker (and static UI) to Cloudflare.
- Runs
If you want to perform the deployment manually, follow these steps in your terminal:
-
Install Wrangler CLI (if not already installed)
npm install -g wrangler
-
Log in to Cloudflare (if not already logged in)
wrangler login
-
Ensure the D1 database exists
wrangler d1 list | grep 'sentryedge' # If you see no output, create it: wrangler d1 create sentryedge
-
Extract and update the
database_idinworker/wrangler.tomlD1_ID=$(wrangler d1 list | grep 'sentryedge' | grep -Eo '[a-f0-9-]{32,}') echo "Database ID: $D1_ID" sed -i '' "s|^database_id = \".*\"|database_id = \"$D1_ID\"|" worker/wrangler.toml # (On Linux, use `sed -i` without `''`.)
-
Build the Next.js UI
cd ui npm install npm run build cd ..
-
Copy static UI files to the Worker
mkdir -p worker/public rsync -a --delete ui/out/ worker/public/
-
Install Worker dependencies
cd worker npm install cd ..
-
Apply D1 migrations
wrangler d1 migrations apply sentryedge
-
Deploy the Worker
cd worker wrangler deploy cd ..
You can run all these steps manually, or simply use ./deploy.sh to automate the process. The script ensures your Cloudflare Worker, static UI, and D1 database are always in sync and ready for production.
To run the Worker locally for development:
From the worker directory, start the development server:
cd worker
npm run startor
wrangler devThe worker will be available at the local address provided by Wrangler.
SentryEdge can automatically parse and normalize log lines from a variety of common sources. The following formats are supported out of the box:
| Format | Example / Notes |
|---|---|
| Apache access log | 127.0.0.1 - - [15/Jun/2025:12:34:56 +0000] ... |
| Nginx access log | 192.168.1.1 - - [15/Jun/2025:12:34:56 +0000] ... |
| Linux syslog | Jun 15 12:34:56 myhost myservice: Something ... |
| Windows Event Log | JSON or XML event log lines |
| Docker JSON log | { "log": "...", "time": "..." } |
| Kubernetes JSON log | { "log": "...", "stream": "stdout", ... } |
| AWS CloudWatch log | { "timestamp": ..., "message": "..." } |
| pfSense log | Jun 15 12:34:56 filterlog: ... |
| PostgreSQL log | 2025-06-15 12:34:56 UTC [1234] ... |
| MySQL log | 2025-06-15T12:34:56.789012Z ... |
| Generic JSON log | { "message": "...", "timestamp": "..." } |
| Key-value log | foo=bar baz=qux |
| Custom/legacy formats | Fallback or extendable via parser |
The parser will auto-detect the format and extract fields like timestamp, service, level, and message.
The log format detection and normalization logic lives in worker/src/log-parsers.ts. Each supported format is defined as an entry in the logParsers array, with a regular expression and a mapping function.
To add or modify a parser:
- Open
worker/src/log-parsers.tsin your editor. - Add a new object to the
logParsersarray with aname,regex, andmapfunction. - The
mapfunction should return an object with at leasttimestamp,service,level, andmessagefields. - Save the file and redeploy your worker.
Example (add a new parser):
{
// Example custom log format
name: "my-custom-format",
regex: /^MYLOG (?<timestamp>\d{4}-\d{2}-\d{2}) (?<message>.+)$/,
map: (match: any) => ({
timestamp: match.groups.timestamp,
service: "my-custom-service",
level: "info",
message: match.groups.message
})
},You can also adjust existing regexes or mapping logic to better fit your environment.
SentryEdge supports flexible log ingestion: you can POST logs as structured JSON, plain text, or base64-encoded text. The worker will auto-detect and normalize the log format, so you can use whatever is most convenient for your source system. Supported ingestion methods:
- Structured JSON — Send a JSON object with explicit fields (
service,level,message). - Raw log line (plain text) — Send a log line as plain text or as the
rawkey in JSON. - Raw log line (base64 encoded) — Send a base64-encoded log line as the
raw_b64key in JSON, or as a base64-encoded body. - Legacy/custom JSON — Send a JSON object with only a
messagefield; the worker will auto-detect the format. - Unknown/Automatic Format Detection — POST any body (plain text or base64); the worker will try to parse it. See below for details and usage examples.
Send a JSON object with explicit fields:
{
"service": "myapp",
"level": "info",
"message": "Hello World"
}Send a single log line as plain text using the raw key. The worker will auto-detect the format (e.g., syslog, Apache, Nginx, etc.):
{
"raw": "Jun 15 12:34:56 myhost myservice: Something happened"
}If your log line contains special characters or you want to avoid encoding issues, send it as base64 using the raw_b64 key:
{
"raw_b64": "PGxvZyBsaW5lIGJhc2U2NCBlbmNvZGVkPi=="
}The worker will automatically decode and parse the log line.
If you send a JSON object with only a message field (and no service or level), the worker will try to auto-detect the format:
{
"message": "Jun 15 12:34:56 myhost myservice: Something happened"
}If you POST a body that is not valid JSON, the worker will attempt to base64-decode it and then parse as a supported log format. This is useful for legacy systems or when sending logs directly as plain text or base64 without a wrapper key.
You can use curl or any HTTP client to POST logs. Here are some examples for each ingestion method:
Structured JSON example:
curl -X POST -H "Content-Type: application/json" \
-d '{"service": "myapp", "level": "info", "message": "Hello World"}' \
https://sentryedge.your-org.workers.dev/logsPlain text example:
curl -X POST -H "Content-Type: application/json" \
-d '{"raw": "Jun 15 12:34:56 myhost myservice: Something happened"}' \
https://sentryedge.your-org.workers.dev/logsBase64 example:
curl -X POST -H "Content-Type: application/json" \
# Example: Send a log line as base64 using raw_b64 (recommended for special characters)
RAW_LINE="Jun 15 12:34:56 myhost myservice: Something happened"
ENCODED=$(echo -n "$RAW_LINE" | base64)
curl -X POST -H "Content-Type: application/json" \
-d '{"raw_b64": "'$ENCODED'"}' \
https://sentryedge.your-org.workers.dev/logsApache and Linux syslog (base64) example:
# Apache log
APACHE_LOG='127.0.0.1 - frank [10/Oct/2020:13:55:36 +0000] "GET /apache_pb.gif HTTP/1.0" 200 2326 "http://www.example.com/start.html" "Mozilla/4.08 [en] (Win98; I ;Nav)"'
APACHE_B64=$(echo -n "$APACHE_LOG" | base64)
curl -X POST -H "Content-Type: application/json" \
-d '{"raw_b64": "'$APACHE_B64'"}' \
https://sentryedge.your-org.workers.dev/logs
# Linux syslog
LINUX_LOG='Jun 15 12:34:56 myhost myservice: Something happened'
LINUX_B64=$(echo -n "$LINUX_LOG" | base64)
curl -X POST -H "Content-Type: application/json" \
-d '{"raw_b64": "'$LINUX_B64'"}' \
https://sentryedge.your-org.workers.dev/logsLegacy/custom JSON example:
Unknown/Automatic (plain text body) example:
curl -X POST --data "Jun 15 12:34:56 myhost myservice: Something happened" \
https://sentryedge.your-org.workers.dev/logsUnknown/Automatic (base64 body) example:
RAW_LINE="Jun 15 12:34:56 myhost myservice: Something happened"
ENCODED=$(echo -n "$RAW_LINE" | base64)
curl -X POST --data "$ENCODED" \
https://sentryedge.your-org.workers.dev/logsFor development, the worker sets Access-Control-Allow-Origin: * to allow cross-origin requests. In production, you should restrict this header to trusted origins only to prevent unauthorized access to your API and protect your data. See the corsHeaders definition in worker/src/index.ts for details and adjust as needed:
const corsHeaders = {
'Access-Control-Allow-Origin': 'https://yourdomain.com', // restrict in production
// ...
};SentryEdge does not include built-in authentication or authorization logic, but you can easily secure access to your endpoints using Cloudflare Zero Trust. By integrating with Cloudflare Access, you can restrict who can view or interact with your worker and UI without changing your application code.
How to protect your SentryEdge deployment with Cloudflare Zero Trust:
- Deploy your worker and/or UI to a Cloudflare-managed domain (e.g., using a custom domain or a workers.dev subdomain).
- In the Cloudflare dashboard, go to the Zero Trust section and set up an Access policy for your application.
- Define rules to allow only specific users, groups, or identity providers (such as Google, GitHub, or your corporate SSO) to access your endpoints.
- Once configured, all requests to your protected routes will require authentication through Cloudflare Access, blocking unauthorized users at the edge before they reach your code or data.
This approach provides strong, flexible security for your logs and dashboard, leveraging Cloudflare’s global network and identity integrations. You do not need to manage passwords or implement custom authentication logic in your worker.
Contributions are welcome! If you want to add new log format parsers, improve documentation, or fix bugs, please open an issue or submit a pull request. For major changes, please open an issue first to discuss what you would like to change.
For more information, see the Cloudflare Workers documentation.
This project is licensed under the terms of the MIT License.
