Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7114b8f
Add reusable operational observability primitives and adapters
elpete Oct 22, 2026
c6d2a40
Fix operational telemetry bounds and cross-engine compatibility
elpete Oct 22, 2026
114dafc
Use JVM clock consistently for Sentry event timestamps
elpete Oct 22, 2026
0787654
Add request saturation, dependency outcomes and queue worker health
elpete Oct 22, 2026
b4d0587
Verify queue health with PostgreSQL and real cbq workers
elpete Oct 23, 2026
1352069
Add automatic telemetry collectors and configurable adapters
elpete Oct 24, 2026
a5c2367
Fix clean-checkout telemetry tests and older Adobe compatibility
elpete Oct 24, 2026
0b6e1f4
Record ad hoc request timings without configuration
elpete Oct 24, 2026
67cbf1a
Require explicit adapters for automatic queue workers
elpete Oct 24, 2026
a18895b
Unify automatic queue snapshots behind provider adapters
elpete Oct 24, 2026
dc2dd15
Require explicit query library instrumentation
elpete Oct 24, 2026
a922121
Delegate query hooks to provider adapters and clarify cbq selection
elpete Oct 25, 2026
2d20bf1
Preserve PostgreSQL identifiers in sanitized query spans
elpete Oct 26, 2026
d3062ae
Avoid loading private feedback capture dependencies [skip ci]
elpete Oct 28, 2026
fc022cb
Fix request timing completion across binary aborts and worker reuse
elpete Nov 2, 2026
a75a3bd
Keep telemetry engine matrix paused [skip ci]
elpete Nov 2, 2026
312e553
Add configurable performance dashboard query recipes
elpete Nov 2, 2026
62c6cb8
Support separate suspect asset dashboard queries [skip ci]
elpete Nov 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 148 additions & 35 deletions ModuleConfig.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -40,42 +40,115 @@ component {
function configure(){
settings = {
// Sentry token
"ServerSideToken" : "",
"ServerSideToken" : "",
// Enable the Sentry LogBox Appender Bridge
"enableLogBoxAppender" : true,
"async" : true,
"enableLogBoxAppender" : true,
"async" : true,
// Min/Max levels for appender
"levelMin" : "FATAL",
"levelMax" : "ERROR",
"levelMin" : "FATAL",
"levelMax" : "ERROR",
// Enable/disable error logging
"enableExceptionLogging" : true,
"enableExceptionLogging" : true,
// Sentry recommends not sending cookie and form data by default
"sendCookies" : false,
"sendPostData" : false,
"sendCookies" : false,
"sendPostData" : false,
// Data sanitization, scrub fields and headers, replaced with "[Filtered]" at runtime
"scrubFields" : [],
"scrubHeaders" : [],
"release" : "",
"environment" : ( !isNull( controller ) ? controller.getSetting( "environment" ) : "" ),
"DSN" : "",
"publicKey" : "",
"privateKey" : "",
"projectID" : 0,
"sentryUrl" : "https://sentry.io",
"scrubFields" : [],
"scrubHeaders" : [],
"release" : "",
"environment" : ( !isNull( controller ) ? controller.getSetting( "environment" ) : "" ),
"DSN" : "",
"publicKey" : "",
"privateKey" : "",
"projectID" : 0,
"sentryUrl" : "https://sentry.io",
// posting to "#sentryUrl#/api/#projectID#/store" is deprecated, but backward compatible
// set to "envelope" to send events to modern "#sentryUrl#/api/#projectID#/envelope"
"sentryEventEndpoint" : "store",
"serverName" : cgi.server_name,
"appRoot" : expandPath( "/" ),
"sentryVersion" : 7,
"sentryEventEndpoint" : "store",
"serverName" : cgi.server_name,
"appRoot" : expandPath( "/" ),
"sentryVersion" : 7,
// This is not arbitrary but must be a specific value. Leave as "cfml"
// https://docs.sentry.io/development/sdk-dev/attributes/
"platform" : "cfml",
"logger" : ( !isNull( controller ) ? controller.getSetting( "appName" ) : "sentry" ),
"userInfoUDF" : "",
"extraInfoUDFs" : {}, // these two settings may be added but they
"showJavaStackTrace" : false,
"throwOnPostError" : false
"platform" : "cfml",
"logger" : ( !isNull( controller ) ? controller.getSetting( "appName" ) : "sentry" ),
"userInfoUDF" : "",
"extraInfoUDFs" : {}, // these two settings may be added but they
"showJavaStackTrace" : false,
"throwOnPostError" : false,
// Master switch for emitted metrics. Automatic collectors and dependency metrics require it.
"enableMetrics" : false,
// Record eligible ColdBox request traces; tracesSampleRate controls trace volume.
"enableRequestTracing" : false,
// Track active eligible requests independently of trace sampling.
"enableRequestSaturation" : false,
// Maximum retained active request observations; excess starts are counted as untracked.
"maxObservedRequests" : 4096,
// Age in seconds used by the long-running-request gauge; this does not declare a timeout.
"requestLongRunningSeconds" : 30,
// Observe compatible cbq execution/publish hooks and their attempt durations.
"enableQueueTracing" : false,
// Count compatible cbq terminal completion/failure hooks separately from attempts.
"enableQueueJobMetrics" : false,
// Fixed queue destination labels allowed on attempt/job metrics.
"queueMetricDestinations" : [],
// Keep bounded, process-local envelope outcomes. Acceptance does not prove indexing.
"enableDeliveryDiagnostics" : false,
// Query tracing is off by default. auto/boxlang try native BoxLang hooks; unavailable hooks leave tracing off.
// lucee requires an application query listener. Library integrations use explicit provider-owned mappings.
// Also accepts callback(context) or a mapping implementing IQueryAdapter.configure(context).
// Example: QueryTelemetryAdapter@qb (requires a qb release containing that optional adapter).
"queryAdapter" : "none",
// Optional initial phase names/header order; measure()/begin() also accept ad hoc names without configuration.
// At most 32 distinct HTTP-safe names per instance. Automatic timing supplies handler/render.
"requestTimingNames" : [],
// Time preEvent/postEvent and preRender/postRender automatically, preserving manual phase APIs.
"enableAutomaticRequestTimings" : false,
// Publish the automatic instance's Server-Timing header when the response still permits headers.
"publishServerTiming" : false,
// Optional callback(event) authorizing header publication; requestFilter still controls collection.
"serverTimingFilter" : "",
// Fixed business workflow names allowed by the manual withOperation API.
"operationNames" : [],
// Fixed provider names allowed on manual workflow observations.
"providerNames" : [],
// Fixed names permitted by withDependency; configured dependencyAdapters add their names automatically.
"dependencyNames" : [],
// Up to 32 {name,mapping,methods:[...],classify:callback|mapping|{mapping,method}} entries.
// WireBox-created instances are intercepted; classifier mappings implement IDependencyClassifier.classify(result,failure).
"dependencyAdapters" : [],
// Writable WireBox AOP generation mapping, used only when this module registers the Mixer listener.
"dependencyAdapterGenerationPath" : "/coldbox/system/aop/tmp",
// Fixed names for the legacy manual worker-health API.
"queueWorkerNames" : [],
// Fixed names for manual queue-health snapshots; configured queues add their names automatically.
"queueNames" : [],
// Fixed executor names; automatic requestExecutors definitions add their names.
"requestExecutorNames" : [],
// Fixed pool names; automatic databasePools definitions add their names.
"databasePoolNames" : [],
"automaticCollectors" : {
// Start one background sampler per service. Disabled by default; also requires enableMetrics and a valid DSN.
"enabled" : false,
// Fixed delay between completed sampling cycles, in seconds, from 1 through 3600. No overlapping cycles.
"intervalSeconds" : 60,
// Sample available heap/thread/CPU/GC values; unsupported values stay absent.
"runtime" : true,
// Emit active/oldest/long-running request gauges; requires enableRequestSaturation.
"requestSaturation" : true,
// Poll local receipt state; never send delivery-health metrics through their own transport.
"deliveryDiagnostics" : false,
// Up to 32 {name,adapter:callback|mapping|{mapping,method},options:{...}} definitions. No default provider.
// One snapshot supplies supported backlog/depth/age/lease fields and worker activity; absent fields stay absent.
// Adapters follow sample(context); context.options is provider-specific configuration.
// cbq example: options:{workerPool:"outbound"} for workers/backlog or {connection:"database",queue:"mail"} for backlog only.
// Both require an explicit adapter:"QueueHealthAdapter@cbq" from a compatible cbq release.
"queues" : [],
// Up to 32 {name,adapter} definitions returning active/max/queued/rejected via callback(context) or ISnapshotAdapter.sample(context).
"requestExecutors" : [],
// Up to 32 {name,adapter} definitions returning active/idle/max/waiters/timeouts via callback(context) or ISnapshotAdapter.sample(context).
"databasePools" : []
}
};

// Try to look up the release based on a box.json
Expand Down Expand Up @@ -103,8 +176,7 @@ component {
);
interceptors = [
{ class : "#moduleMapping#.interceptors.QueueObservability" },
{ class : "#moduleMapping#.interceptors.Observability" },
{ class : "#moduleMapping#.interceptors.QueryObservability" }
{ class : "#moduleMapping#.interceptors.Observability" }
];
interceptorSettings = { customInterceptionPoints : [ "onSentryEventCapture" ] };
}
Expand All @@ -113,13 +185,20 @@ component {
* Fired when the module is registered and activated.
*/
function onLoad(){
// Incorporate defaults into settings
if ( settings.queryAdapter == "auto" || settings.queryAdapter == "boxlang" ) {
wirebox
.getInstance( "SentryService@sentry" )
.getObservability()
.configureQueryInstrumentation( getApplicationMetadata().name );
// Initialize only when an explicitly configured integration needs startup work.
if (
!isSimpleValue( settings.queryAdapter ) || settings.queryAdapter != "none" || (
isStruct( settings.automaticCollectors ) && settings.automaticCollectors.keyExists( "enabled" ) && isBoolean(
settings.automaticCollectors.enabled
) && settings.automaticCollectors.enabled
) || ( isArray( settings.dependencyAdapters ) && settings.dependencyAdapters.len() )
) {
var telemetry = wirebox.getInstance( "SentryService@sentry" ).getObservability();
telemetry.setAdapterInjector( wirebox );
telemetry.configureQueryInstrumentation( getApplicationMetadata().name );
registerDependencyAdapters( telemetry );
}

binder.mapDSL( "sentryCache", "#moduleMapping#.models.dsl.CacheDSL" );
wirebox.registerDSL( "sentryCache", "#moduleMapping#.models.dsl.CacheDSL" );
settings.scrubFields.addAll( SCRUB_FIELDS );
Expand All @@ -144,6 +223,40 @@ component {
}
}

private function registerDependencyAdapters( required any telemetry ){
var bindings = arguments.telemetry.dependencyBindings();
if ( !bindings.len() ) return;
if (
!binder
.getListeners()
.some( function( listener ){
return compareNoCase( listener.class, "coldbox.system.aop.Mixer" ) == 0;
} )
) {
binder.listener(
class = "coldbox.system.aop.Mixer",
properties = { "generationPath" : settings.dependencyAdapterGenerationPath },
register = true
);
}
binder
.mapAspect( "SentryDependencyInterceptor@sentry", false )
.to( "#moduleMapping#.models.telemetry.DependencyInterceptor" );
var mappings = [];
var methods = [];
for ( var binding in bindings ) {
if ( !arrayFindNoCase( mappings, binding.mapping ) ) mappings.append( binding.mapping );
for ( var method in binding.methods ) {
if ( !arrayFindNoCase( methods, method ) ) methods.append( method );
}
}
binder.bindAspect(
binder.match().mappings( mappings.toList() ),
binder.match().methods( methods.toList() ),
"SentryDependencyInterceptor@sentry"
);
}

/**
* Fired when the module is unregistered and unloaded
*/
Expand Down
2 changes: 1 addition & 1 deletion box.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name":"Sentry",
"author":"Ortus Solutions <info@ortussolutions.com>",
"version":"2.2.1",
"version":"2.2.2",
"slug":"sentry",
"type":"modules",
"homepage":"https://github.com/coldbox-modules/sentry",
Expand Down
File renamed without changes.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading