BoxLang 🚀 A New JVM Dynamic Language Learn More...
errorAlerts emails developers when a ColdBox application
has an unhandled exception or logs an ERROR or
FATAL message.
By default, the first three occurrences of one error send emails immediately. Later occurrences are counted and sent in one digest. This limit prevents a large error burst from flooding your inbox.
The module registers itself with ColdBox, so you do not need to edit your LogBox configuration.
errorAlerts sends mail through cbmailservices,
which is installed automatically. Its default CFMail
protocol uses the SMTP settings from your Adobe ColdFusion
Administrator, Lucee Administrator, or BoxLang configuration.
Run this command from your ColdBox application's root folder:
box install erroralerts
Create config/modules/errorAlerts.cfc in your application:
component {
function configure(){
return {
to : "[email protected]",
from : "[email protected]",
subjectPrefix : "[My App]"
};
}
}
Replace both example addresses with addresses that your mail server accepts.
Important defaults:
development.to is blank.ERROR and FATAL messages produce alerts.Restart or reinitialize your ColdBox application after adding or changing the configuration.
Add a temporary action to one of your ColdBox handlers:
function testError( event, rc, prc ){
throw(
type = "MyApp.TestError",
message = "Testing errorAlerts"
);
}
Visit the action in your browser. You should receive an email with the error details, request details, source code, stack frames, and runtime versions.
If an error starts deep inside framework or ORM code, the email also finds the first frame from your application. The email labels this frame and its source as "Application caller." A row such as "... 5 frames omitted" marks a shortened stack.
The email adjusts to small screens. On a screen narrower than 600 pixels, each label moves above its value so the value gets the full width, section padding shrinks, and long source lines and file paths wrap instead of running off the side. Every element also carries its own inline styles, so a mail client that ignores embedded style blocks, such as Outlook on Windows, still shows the normal two-column layout. There is no setting to configure here.
Remove the test action after confirming delivery.
No extra code is required. Exceptions that reach ColdBox's exception handler are captured automatically:
function saveOrder( event, rc, prc ){
// If this throws and is not caught, errorAlerts sends an alert.
orderService.save( rc );
}
You can also send alerts for errors that your application catches.
Inject a LogBox logger and log at ERROR or FATAL:
component {
property name="log" inject="logbox:logger: {this}";
function chargeCard( required struct payment ){
try {
return paymentService.charge( arguments.payment );
} catch ( any exception ) {
log.error(
message = "Card charge failed",
extraInfo = exception
);
rethrow;
}
}
}
Passing the caught exception as extraInfo lets the alert
include its type, detail, and stack frames.
The module groups errors by LogBox category, the first 200 message
characters, the top stack frame, and the first application stack
frame. It replaces numbers and UUIDs in messages by default. For
example, Order 123 failed and Order 456
failed belong to the same group.
Database errors often have the same framework frame at the top of the
stack. The application frame keeps failures from different call sites
in separate groups. applicationFramePrefixes defines
which paths belong to the application.
With the defaults, if the same error occurs five times within ten minutes:
Throttle counters are stored in memory and are separate for each application server.
ColdBox's RestHandler can report one exception twice
with different categories and messages. Throttling cannot group those
two reports. The module drops the logged copy and keeps the
onException announcement. Only the announcement has the
tagContext needed for stack frames and source code. See
suppressDuplicateFrameworkLogs in the advanced settings.
Most examples belong in the struct returned by
config/modules/errorAlerts.cfc. Examples that add another
function show the complete file.
Alerts are active in every environment. To disable one environment,
add a function with that environment's name to
config/modules/errorAlerts.cfc. ColdBox passes the merged
settings to this function after configure() runs.
component {
function configure(){
return {
to : "[email protected]",
from : "[email protected]"
};
}
// ColdBox runs this only when the detected environment is `development`.
function development( settings ){
settings.enabled = false;
}
}
The function name must match the detected environment name. For
example, use staging( settings ) for the staging environment.
Change settings directly. ColdBox ignores the function's
return value.
If you prefer to keep all environment overrides in one file, a
development() function in config/Coldbox.cfc
that sets moduleSettings.errorAlerts.enabled = false
works too.
levelMin : "FATAL",
levelMax : "WARN"
Valid LogBox levels are OFF, FATAL,
ERROR, WARN, INFO, and DEBUG.
This example sends one immediate email per matching error every five minutes:
throttle : {
maxPerWindow : 1,
windowSeconds : 300,
maxTrackedSignatures : 500
}
You may override only the nested keys you need. Module startup
restores any missing throttle defaults.
ignoreExceptionTypes : [
"EventHandlerNotRegisteredException",
"MyApp.ExpectedException"
],
ignoreCategories : [
"cbmailservices",
"coldbox.system.Bootstrap",
"coldbox.system.web.services.HandlerService",
"myapp.healthcheck"
]
Category matching ignores case and checks the start of the category. Keep the two default categories:
cbmailservices prevents a failed alert email from
generating another alert.coldbox.system.Bootstrap prevents duplicate emails for
unhandled exceptions.Set userProvider to a closure that receives WireBox and
returns text for the signed-in user. This example uses cbauth:
userProvider : function( wirebox ){
var auth = wirebox.getInstance( "AuthenticationService@cbauth" );
if ( !auth.isLoggedIn() ) {
return "";
}
var user = auth.getUser();
return "#user.getName()# <#user.getEmailAddress()#> (id #user.getId()#)";
}
Return "" when nobody is signed in. A provider
error is ignored so it cannot stop the alert. The row then uses emptyValueText.
A JSON request body does not appear in rc. Enable
request bodies when that input is needed for API errors:
includeRequestBody : true
JSON objects and arrays use the masking settings at every visited
level. Output stops at requestBodyMaxLength. Form posts
stay excluded because rc already shows their masked fields.
For local testing without SMTP, use synchronous delivery in config/modules/errorAlerts.cfc:
component {
function configure(){
return {
to : "[email protected]",
from : "[email protected]",
deliveryMode : "send"
};
}
}
Then create config/modules/cbmailservices.cfc:
component {
function configure(){
return {
defaultProtocol : "default",
mailers : {
"default" : {
class : "File",
properties : {
filePath : "/mail-spool"
}
}
}
};
}
}
Trigger a test error, then open the generated HTML file in the
application's mail-spool folder. Do not use the File
protocol as your production mailer.
Alert emails can include request data and secrets. Masking is
off by default because each application uses
different field names. Add your sensitive field names to
maskKeys before production use.
maskKeys
maskKeys : [
// A starter list to copy. Keep what applies and add your own field names.
"password", "passwd", "token", "secret", "apikey", "api_key",
"authorization", "creditcard", "cvv",
"ssn", "cardNumber", "client_secret", "access_token", "refresh_token"
]
A matching field value becomes *** masked *** in
rc, JSON bodies, extraInfo, and query
strings. Matching ignores case but requires the complete field name.
For example, password does not match
newPassword or passwordConfirm. List every
sensitive name used by your forms and APIs. The module uses one hash
lookup per field, so a long list does not make each lookup slower.
Masking a container key, such as auth or
profile, hides the entire structure under it with one entry.
maskKeyPatterns
Use a wildcard pattern when outside systems provide many versions of
one field name. For example, ssn does not match
indemnitor1SocialSecurityNumber. This pattern does:
maskKeyPatterns : [ "*socialsecurity*" ]
* matches any number of characters. It is the only
special character. Matching ignores case and covers the complete key.
The module checks patterns only when maskKeys has no
exact match. Pattern results are cached for repeated field names.
Keep patterns narrow. For example, *token* also hides
useful fields such as tokenCount and
nextPageToken. Version 2.1.0 removed automatic substring
matching for this reason.
longValueMaxLength and longValueExemptKeys
Payloads can contain large strings such as files or API responses. A
value longer than longValueMaxLength keeps its beginning
and adds its original size:
JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PC9UeXBlL0NhdGFsb2… [truncated, 245120 chars total]
Set longValueMaxLength : 0 to disable the per-value
limit. Keys in longValueExemptKeys skip this limit. The
default exemptions keep stack traces readable. Complete section limits
still apply.
The sanitizer stops after it collects enough content for the section
limit. extraInfo uses extraInfoMaxLength,
and JSON bodies use requestBodyMaxLength. Large
structures stop early and show [N more keys]. Every
displayed value passes the mask check before a limit can stop the walk.
| Situation | Reach for |
|---|---|
| You know the field name | maskKeys
|
| Many vendor spellings of one field | maskKeyPatterns
|
| A whole container with no diagnostic value | the
container's key in maskKeys
|
| Huge values bloating or slowing alerts | Nothing.
longValueMaxLength is on by default. |
Key masking needs a field name. It cannot mask bound SQL values,
exception detail, exception extendedInfo,
non-JSON bodies, or secrets inside plain strings. Change a logging
call if it passes a secret in one of these places. Length limits
reduce the amount shown but do not make this content safe.
An XML or plain-text body is printed as sent, up to
requestBodyMaxLength. A body recognized as JSON is either
walked and masked or reported by size. JSON is not printed raw when
parsing or masking cannot finish.
Engines do not always agree about valid JSON. Adobe ColdFusion 2023
rejects a trailing comma that Lucee 5 and BoxLang accept. An engine
may treat malformed JSON as plain text and print it as sent. Keep
includeRequestBody off when request bodies may contain secrets.
extraInfo
The sanitizer walks plain structs and arrays. It sends a component
instance directly to the JSON serializer. Keys inside the
component are not checked against maskKeys. A
component with a cached token may print that token.
Queries also go directly to the serializer. Their rows often provide useful details. Objects often contain framework state and can have large data graphs, so logging them is less useful and more expensive.
The fix belongs at the logging call site. Log the values you need, not the whole object:
// This can print unmasked component data.
logger.error( "Charge failed", { gateway : paymentGateway } );
// Log plain fields so maskKeys can check them.
logger.error( "Charge failed", {
gateway : "AcmePay",
statusCode : response.statusCode,
orderId : order.getId()
} );
The module does not call getMemento() on an object. On a
Quick or cborm entity, that call can query relationships, walk an
unlimited object graph, or throw another error.
The rc table shows an object as [object
com.foo.Bar]. The table only supports short strings limited by
rcValueMaxLength. This output does not mean the object's
keys were masked.
| Setting | Default | Description |
|---|---|---|
enabled
| true
| Master switch for all alerts. |
to
| ""
| Alert recipient. Required by the built-in email notifier. |
from
| ""
| Sender address. Uses to when blank. |
subjectPrefix
| ""
| Text added to subjects. Uses the ColdBox application name when blank. |
deliveryMode
| "queue"
| "queue" sends in the background;
"send" sends synchronously. |
levelMin
| "FATAL"
| Lowest numeric end of the LogBox severity range. |
levelMax
| "ERROR"
| Highest numeric end of the LogBox severity range. Set to
"WARN" to include warnings. |
throttle.maxPerWindow
| 3
| Immediate emails allowed for one error signature per window. |
throttle.windowSeconds
| 600
| Length of the fixed throttle window. |
ignoreCategories
| See below | Category prefixes that never produce alerts. |
ignoreExceptionTypes
| []
| Exception types that never produce alerts. |
The default ignored categories are [
"cbmailservices", "coldbox.system.Bootstrap" ].
throttle and includeScopes are structs.
ColdBox replaces a complete nested struct when an application
overrides one key. The module restores missing defaults during
startup, so partial overrides are safe:
moduleSettings = {
errorAlerts : {
to : "[email protected]",
includeScopes : { session : true }, // rc, cgi and extraInfo keep their defaults
throttle : { windowSeconds : 60 } // maxPerWindow and maxTrackedSignatures keep theirs
}
};
| Setting | Default | Description |
|---|---|---|
notifier
| "EmailNotifier@errorAlerts"
| WireBox ID of the notification provider. |
suppressDuplicateFrameworkLogs
| true
| Drops the LogBox copy of an exception that
RestHandler also announces. Category rules cannot
find every copy because each handler has a different category.
Set this to false only after unregistering UnhandledExceptionCapture. |
throttle.maxTrackedSignatures
| 500
| Maximum error signatures kept in memory. New signatures send unthrottled when the limit is reached. |
digestFlushSeconds
| 60
| How often expired throttle windows are checked for pending digests. |
normalizeSignatures
| true
| Replaces digit runs and UUIDs before errors are grouped. |
maxBodyBytes
| 102400
| Maximum rendered email size. Lower-priority sections are removed first. |
rcValueMaxLength
| 200
| Maximum rendered length of each request-collection value. |
stackFrames
| 10
| Maximum stack frames shown. Driver frames that are not file paths are skipped after the first frame. The first application frame is added when it falls past this limit. |
applicationFramePrefixes
| Conventional ColdBox folders | Paths that mark
application stack frames. Defaults cover handlers, models,
views, layouts, config, interceptors, and
modules_app. Installed dependencies under
/modules/ are excluded. |
digestSampleSize
| 5
| Maximum distinct routes and client IP addresses listed in
a digest. 0 disables both lists. A client controls
X-Forwarded-For, so treat the IP address as a hint
rather than proof. |
includeScopes
| { rc: true, cgi: true, extraInfo: true, session:
false }
| Controls which diagnostic sections are included.
session adds a sessionId row so alerts
from one visitor can be tied together. |
codeSnippetLines
| 5
| Source lines shown either side of the failing line, with
the failing line highlighted. 0 turns the snippet
off. Costs one file read per alert and puts application source
in the email. |
userProvider
| ""
| Closure that receives WireBox and returns text for the
signed-in user. See the recipe above. The user row
only appears when this is set. |
relativePaths
| true
| Trims the application root from file paths so a path
reads /handlers/Main.cfc. Paths outside the
application root are always shown in full. Set
false to keep every path absolute. |
includeQueryParams
| true
| Include bound values from a failed query. These values
can help reproduce the error, but they may contain user input
that maskKeys cannot hide. |
maskQueryString
| true
| Apply maskKeys and
maskKeyPatterns to the query string shown in the
Request section. Turning this off can put a secret from a GET
request in email. |
emptyValueText
| "N/A"
| Placeholder shown for a request value the request did not provide. |
includeHeaders
| [ "Content-Type" ]
| Request headers shown in the Request section.
Cookie, Authorization, and
Proxy-Authorization are always blocked. |
includeRequestBody
| false
| Include bodies that are not form posts. JSON objects and arrays are masked. JSON that cannot be walked is reported by size. Non-JSON text is shown as sent because it has no field names to mask. |
requestBodyMaxLength
| 4000
| Maximum characters of request body shown. |
maskKeys
| []
| Exact field names to mask in rc, JSON
bodies, extraInfo, and query strings. Nested
walking stops after five levels. Matching ignores case. Masking
is off by default. |
maskKeyPatterns
| []
| Wildcard field-name patterns, checked only when the exact
maskKeys lookup misses. * matches any
run of characters; matching ignores case and covers the whole name. |
longValueMaxLength
| 500
| Per-value cap for string values found while walking
extraInfo or a JSON body. Longer values keep this
many characters plus a label stating the real size.
0 disables the per-value cap. |
longValueExemptKeys
| [ "_stacktrace", "stacktrace" ]
| Keys whose values skip the per-value cap, matched exactly ignoring case, so stack traces keep their frames. The whole-output caps still apply. |
extraInfoMaxLength
| 2000
| Maximum characters of sanitized extraInfo
shown, and the collection budget for its walk. |
exceptionFieldMaxLength
| 2048
| Maximum characters of the exception detail
and extendedInfo fields shown. 0
disables the cap; maxBodyBytes still limits the
whole email. |
Masking is opt-in and has its own section above: see Masking and sanitization for the starter list, wildcard patterns, and long-value truncation.
Masking is now opt-in. Applications that relied on the old default
list must set maskKeys. Copy the starter list from Masking and sanitization and
keep the names your application uses. This release also masks nested
rc and JSON fields. Strings longer than
longValueMaxLength now show a shortened value and size
label. Set the limit to 0 to keep complete values.
Two behavior changes to know about:
maxPerWindow extra emails.relativePaths now defaults to true, so
file paths inside the application root render as
/handlers/Main.cfc instead of the full absolute path.
Set it back to false to keep absolute paths.
maskKeys matching changed from substring to exact in
2.1.0. A key such as apiToken, which the
token entry used to mask by substring, is no longer
masked unless you list it explicitly. Before upgrading, audit your
forms and add every sensitive field name to maskKeys in config/modules/errorAlerts.cfc.
Check these items in order:
to is not blank and enabled
is still true.config/modules/errorAlerts.cfc and
config/Coldbox.cfc for an environment function that
turns enabled off for the current environment.ERROR or
FATAL. WARN and lower levels are ignored
by default.deliveryMode : "send" to
remove the background queue delay.errorAlerts.Mail delivery failures are intentionally prevented from breaking the original request. They are written to standard error instead of being thrown back into your application.
This is usually throttling. The first three matching errors send immediately; the rest appear in a digest after the window closes.
Throttle state is kept per JVM. In a web farm, each server can send
up to maxPerWindow immediate alerts. Restarting the
application or running fwreinit also resets the counters.
Pending throttle data lives only in memory. Restarting or reinitializing the application before the window is flushed discards the pending digest.
The module adds an appender to ColdBox's root LogBox logger and
registers an onException interceptor. Both use the same
capture process. The process applies ignore rules, groups matching
errors, throttles repeats, sanitizes the payload, and calls the
notifier. Internal failures do not escape into the original request.
To deliver alerts somewhere other than email, create a singleton that implements the notifier interface:
component singleton implements="errorAlerts.models.notifiers.INotifier" {
void function sendNotification( required struct payload ){
try {
// Send payload to your alert service.
// payload.type is either "error" or "digest".
} catch ( any exception ) {
// A notifier must not throw into the original request.
}
}
}
Map the component in WireBox, then set its mapping ID:
notifier : "MyNotifier@myapp"
A custom notifier does not require to. It must handle
its own failures. Do not log those failures at ERROR
because that can create an alert loop.
fwreinit.maskKeys ships empty, and the
exact match means every sensitive field name your application
actually uses has to be listed. Review Masking and sanitization
before production use.box run-script install:dependencies
box run-script start:2023
box testbox run
To use another engine, run its start script:
start:boxlang, start:lucee5,
start:lucee6, or start:2025. Prefix the
script with box run-script. All engines use the same
port, so stop the active engine first.
The harness home page at http://127.0.0.1:60310/ lists every failure scenario. It can send alerts to a local SMTP server for inspection. See docs/test-harness.md.
Releases must use box run-script release; do not run
box publish directly from the repository root. See RELEASE.md for the routine and docs/release-process.md for why.
MIT
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
rc entries use the full width instead of a squeezed column.
Section padding shrinks from 20 to 12 pixels, and long source lines and stack
frame paths wrap instead of running off the side. Measured against the test
harness on a 360 pixel screen, the email went from 1249 pixels wide to 344.<table> fragment, which left phones to guess the layout width.exceptionFieldMaxLength, default 2048, controls how many characters of
the exception detail and extendedInfo fields the email shows. Both
limits were hardcoded at 1024 before. Set 0 to disable the cap;
maxBodyBytes still limits the whole email.<style> block holding a single
@media only screen and (max-width:600px) rule, and elements carry ea-
prefixed classes. Every element still carries the inline styles it had
before, so a mail client that ignores embedded style blocks, such as Outlook
on Windows, renders the same two-column layout as earlier versions.assemble() now subtracts the measured length of the document head and
footer from the maxBodyBytes budget instead of assuming a fixed 600
characters. Without this the larger head could push an email past the cap.white-space:pre and a table cannot shrink below its content. This behavior
is unchanged from earlier versions and only affects wide screens; small
screens now wrap those lines.maskKeys defaults to an empty array. The old
defaults were password, passwd, token, secret, apikey, api_key,
authorization, creditcard, and cvv. Applications that relied on those
defaults must now list their own sensitive fields. Applications that already
set maskKeys are not affected. The README provides a starter list.rc values and JSON bodies. Earlier
versions only checked top-level keys. JSON array bodies are also walked.includeScopes override
could remove rc, cgi, and extraInfo, which made alert building fail. A
partial throttle override could also disable throttling. Module startup now
restores missing keys without replacing application values.maskKeyPatterns adds wildcard field matching. * matches any number of
characters. Matching ignores case, covers the complete field name, and runs
only when maskKeys has no exact match.longValueMaxLength, default 500, limits each string in extraInfo or a
JSON body. Truncated values include their original size. Set it to 0 to
disable the per-value limit.longValueExemptKeys, default [ "_stacktrace", "stacktrace" ], keeps
selected values from using the per-value limit.extraInfoMaxLength, default 2000, makes the old hardcoded section limit
configurable.tests/specs/perf measures alert building with large payloads. Run it with
?directory=tests.specs.perf.[N more keys]. Every displayed
value still passes the mask check.rc
key and header.rc table now end with the size label instead of a
bare ellipsis.rc value now renders [null] instead of hiding the complete rc
table.maskKeys now checks extraInfo values up to five levels deep. This also
protects request headers that RestHandler places inside extraInfo, including
Authorization. The Request section's header blocklist does not cover this
separate path.detail, and exception extendedInfo remain
unmasked because they have no field names. The README now states this limit.onException. The reports use different categories
and messages, so throttling could not combine them. Category rules also could
not match every handler category.onException announcement contains the
tagContext needed for stack frames, application attribution, and source code.RestHandler.onError() logs without announcing, so its report is kept. This
covers errors in preHandler, postHandler, and aroundHandler.suppressDuplicateFrameworkLogs, default true, controls RestHandler
duplicate removal. Set it to false only after unregistering the module's
UnhandledExceptionCapture interceptor.maxPerWindow extra emails.relativePaths now defaults to true: paths inside the application root
render as /handlers/Main.cfc instead of the full absolute path. Paths
outside the root are still shown in full. Set the flag back to false for
absolute paths.stackFrames limit. applicationFramePrefixes defines application
paths.url row in the Request section, e.g.
GET https://example.com/posts/hello?page=2, with maskKeys applied to the
query string.elapsed row showing how long the request had been running when it
failed, on engines that expose the request start time (Lucee, BoxLang).digestSampleSize
limits both lists, and 0 disables them. A client can control
X-Forwarded-For, so the IP address is a hint rather than proof.These changes only affect project development and release tasks.
release:existing-tag publishes a tag that already points to HEAD. This
supports CI jobs triggered after a tag is created.release:hotfix is now also available under the clearer name
release:skip-tests. Both names still work.test:engines runs and reports every engine even after one fails.git pull --ff-only instead of git checkout -f. Starting a
release can no longer discard uncommitted work.maskKeys now matches complete field names and ignores case.
It no longer uses substring matching. For example, token does not match
apiToken or tokenCount. List every sensitive field name explicitly.sqlState, which is what Adobe reports for database errors.codeSnippetLines
controls its size.includeQueryParams).extendedInfo (where cbValidation puts
field-level errors).emptyValueText. New rows:
origin (the file and line that broke, even for a plain log.error() call),
user (via the new userProvider closure setting), routePattern,
routeName, routedModule, routedNamespace, view, layout, isAjax,
machine, context (web request / scheduled task / background thread),
memory (JVM heap use), sessionId (opt-in via includeScopes.session),
templatePath, forwardedFor, forwardedProto, accept,
acceptLanguage, https, and serverPort.includeHeaders, which defaults to Content-Type.
Cookie and Authorization are always blocked.includeRequestBody, off by default)
for JSON and API endpoints, masked with maskKeys and capped at
requestBodyMaxLength.maskQueryString applies maskKeys to GET query strings.relativePaths trims the application root from file paths.environments setting. Alerts now run in every environment. To disable
one environment, add a matching function to config/modules/errorAlerts.cfc
and set settings.enabled = false. The README contains an example.
$
box install erroralerts