A minimal, self-contained ColdBox module that ships LogBox entries to a
Stachebox instance over HTTP.
It replaces the logstash module (and its bundled cbelasticsearch, hyper
and cbrestbasehandler dependencies) for applications that only send logs
to a remote Stachebox — the transmission: "api" mode.
The stock setup was: logstash module in api mode, posting entries to a
central Stachebox instance. No local Elasticsearch exists
on the application servers. Despite that, the application intermittently
failed to boot after a restart:
lucee.runtime.exp.LockException: a timeout occurred after 20 seconds trying to
acquire a exclusive lock with name [module....activation.logstash]
followed by key [cbController] doesn't exist on every request — the site
never came back online.
Root cause, in the logstash module (v3.1.1):
- In
apimode the module registerslogstash.models.logging.APIAppender. APIAppenderextendscbelasticsearch.models.logging.LogstashAppenderand only overrideslogMessage(). It inheritsonRegistration().- LogBox calls
onRegistration()when the appender is registered — during module activation, inside ColdBox's exclusive per-module activation lock. - The inherited
onRegistration()runsensureDataStream(): ILM policy, ingest pipeline, index/component templates — all against thecbelasticsearchclient, whose unconfigured default host is127.0.0.1:9200. - With no local Elasticsearch that call blocks until it times out, while
holding the activation lock. Concurrent requests hit the 20-second lock
timeout, ColdBox never finishes loading,
application[cbController]is never set, and the application stays down.
So an appender whose only job was "POST JSON to a URL" was taking the site down over an Elasticsearch it was never supposed to talk to — and dragged in three dependency modules to do it.
Everything the old stack actually did for us fits in one appender CFC with no
dependencies (models/StacheboxAppender.cfc):
- Marshal the log event into the same entry format the
cbelasticsearchLogstashAppender produced (ECS-style keys, source-contextframesfor exceptions,error.stack_trace/frames/extrainfoas JSON strings,yyyy-mm-dd'T'HH:nn:ssZZtimestamps). - Sign each entry with the identical
stachebox.signaturehash (same fields, same order), so occurrences keep grouping with entries logged by the old module. - Send
{ "entry": ..., "dataStream": ... }with a Bearer token to the Stachebox API from a fire-and-forgetcfthread— the request never waits on the log target, and a send failure only prints to stderr.
Module activation does zero I/O: onLoad() just registers the appender on
the root logger. An unreachable Stachebox can no longer affect application
startup or requests.
Deliberately dropped from the old module: direct-to-Elasticsearch mode, the
log-receiving API endpoints, detached appenders, and the userInfoUDF hook.
If per-user context is ever needed again, extend the user key in
marshallLogObject().
Available on ForgeBox:
box install singleFileStacheboxAppenderSource and issue tracker: github.com/akitogo/singleFileStacheboxAppender
In config/Coldbox.cfc (or config/modules/singleFileStacheboxAppender.cfc):
moduleSettings = {
"singleFileStacheboxAppender" = {
"apiUrl" : "https://stachebox.example.com/logstash/api/logs",
"apiAuthToken" : "<bearer token>",
"applicationName" : "my-app",
"levelMin" : "FATAL",
"levelMax" : "INFO"
}
};| Setting | Default | Purpose |
|---|---|---|
enabled |
true |
Master switch |
apiUrl |
(empty — appender not registered) | Stachebox log intake endpoint |
apiAuthToken |
(empty) | Authorization: Bearer token |
applicationName |
application name | Grouping label in Stachebox |
dataStream |
logs-coldbox-logstash-appender |
Data stream name sent with each entry |
levelMin |
FATAL |
Minimum severity |
levelMax |
INFO |
Maximum severity |