@coralogix/roku
v0.2.0
Published
Official Coralogix SDK for Roku
Readme
Official Coralogix SDK for Roku
Real User Monitoring for Roku channels: sessions, logs, errors, crashes, views and network requests, sent to Coralogix. Requires Roku OS 11 or later; crash reporting requires Roku OS 13 or later.
Links
Installation
ropm is a package manager for Roku, based on npm. If your channel doesn't use ropm yet, follow its installation guide. Then install the SDK:
ropm install @coralogix/rokuropm prefixes the SDK's functions with coralogix_roku_, as used throughout
this page.
Usage
Call coralogix_roku_initialize in main.brs, right after screen.show():
sub Main()
screen = CreateObject("roSGScreen")
port = CreateObject("roMessagePort")
screen.setMessagePort(port)
screen.CreateScene("MainScene")
screen.show()
coralogix_roku_initialize({
public_key: "your-rum-public-key",
application: "my-channel",
coralogixDomain: "EU2",
version: "1.4.0",
environment: "production",
labels: { tier: "premium" },
user_context: { user_id: "user-1", user_name: "Jane", user_email: "[email protected]" },
sessionConfig: { sessionSampleRate: 100, inactivityTimeoutMinutes: 120 }
}, screen.getGlobalNode())
' ...the rest of your channel setup
end subcoralogix_roku_initialize returns true when the SDK starts. If an option is invalid,
it returns false, sends nothing, and the console names the option. The SDK skips
a label with an unsupported value, or a pattern that doesn't compile, and warns
on the console.
After initialize, every component reaches the SDK as m.global.cxRum:
m.global.cxRum.callfunc("reportInfo", "home screen shown")In a Task, read it once and keep the reference:
sdk = m.global.cxRum
sdk.callfunc("reportInfo", "catalog loaded")m.global.cxRum is invalid before initialize and after shutdown, and a
callfunc on invalid crashes the thread. Guard every call that can run
outside that window:
if m.global.cxRum <> invalid then m.global.cxRum.callfunc("reportInfo", "home screen shown")A scene's init() runs during CreateScene, before initialize, so it always
sees invalid. Report from init() only after Main signals that the SDK is
ready, for example through an interface field your scene observes and Main
sets after coralogix_roku_initialize returns.
A reference kept in a Task stays safe after shutdown: report calls on it do
nothing. Every call returns immediately; the SDK sends records in batches from
its own thread.
Configuration options
| Option | Type | Required | Description |
| --- | --- | --- | --- |
| public_key | String | Yes | Your RUM public key. |
| application | String | Yes | Your application name in Coralogix. |
| coralogixDomain | String | Yes* | Your Coralogix region: EU1, EU2, US1, US2, US3, AP1, AP2, AP3. |
| coralogixDomainUrl | String | Yes* | Your ingress URL, used instead of coralogixDomain. |
| version | String | No | Your channel's version. Defaults to the version in your manifest. |
| environment | String | No | For example production or staging. |
| labels | Object | No | Labels added to every record. Values are strings, numbers or booleans. |
| user_context | Object | No | The viewer: user_id, user_name, user_email, account_id, account_name, user_metadata. Change it later with setUserContext. |
| sessionConfig | Object | No | { sessionSampleRate, inactivityTimeoutMinutes }. sessionSampleRate: the percentage of sessions to record, 0–100. Default 100. inactivityTimeoutMinutes: how long a session lasts with no activity, a whole number of minutes from 15 to 240. Default 120 (2 hours). See Sessions. |
| instrumentations | Object | No | { custom, errors, network }, all true by default. See Instrumentations. |
| ignoreUrls | Array | No | Network requests not to report. See Ignore URLs. |
| ignoreErrors | Array | No | Errors not to report. See Ignore errors. |
| ignoredExitEvents | Array | No | Exit codes that are not crashes, such as "EXIT_SETTINGS_UPDATE", added to the defaults: EXIT_UNKNOWN, EXIT_POWER_MODE, EXIT_IDLE_AUTO_EXIT, EXIT_DIAL_DELETE, EXIT_USER_KILL, EXIT_USER_NAV. A Home press is already not a crash. |
| traceParentInHeader | Object | No | { enabled, options: { allowedTracingUrls } }. See Traces. |
| batchConfig | Object | No | { scheduledDelayMillis }: how often the SDK sends records, 2000–30000 ms. Default 2000. |
| proxyUrl | String | No | Send records to your own relay. The SDK passes the Coralogix address in a cxforward query parameter. |
| ignoreProxyUrlParams | Boolean | No | Send to proxyUrl without the cxforward parameter. Default false. |
| debug | Boolean | No | Print SDK activity to the device console. Default false. See Troubleshooting. |
* Set one of coralogixDomain or coralogixDomainUrl.
Instrumentations
Pass false to turn one off:
| Key | Covers |
| --- | --- |
| custom | Custom logs: reportLog and the level methods |
| errors | reportError and crash reports |
| network | CxUrlTransfer and reportNetworkRequest. When false, the SDK adds no traceparent header. |
coralogix_roku_initialize({
' ...
instrumentations: { network: false }
}, screen.getGlobalNode())Custom logs
m.global.cxRum.callfunc("reportInfo", "playback started", {
data: { asset_id: "movie-8842", bitrate: 4500 },
labels: { feature: "playback" }
})| Method | Severity |
| --- | --- |
| reportDebug | Debug |
| reportVerbose | Verbose |
| reportInfo | Info |
| reportWarn | Warning |
| reportErrorLog | Error |
| reportCritical | Critical |
Each takes a message and optional { data, labels }. The SDK cuts a message at
2048 characters, and drops data over 3584 characters with a console warning.
When the level is a variable, use reportLog with a value from getSeverity:
severity = m.global.cxRum.callfunc("getSeverity")
m.global.cxRum.callfunc("reportLog", severity.WARN, "cache miss", { data: { key: "home" } })[!NOTE] BrightScript lowercases associative-array keys, so
data: { assetId: 1 }arrives in Coralogix asassetid. Use snake_case keys, or build the object case-sensitive before adding any keys:data = CreateObject("roAssociativeArray") data.SetModeCaseSensitive() data["assetId"] = 1
SetModeCaseSensitive()works only on a new object, before you add keys, and doesn't carry into nested objects: set it on each level. The same applies tolabels.
Error reporting
Report errors from your try/catch. The SDK sends the message, error code and
stack trace:
try
startPlayback(asset)
catch e
m.global.cxRum.callfunc("reportError", e, {
data: { asset_id: asset.id },
labels: { feature: "playback" }
})
end tryReport a failure that didn't throw, as an object or a message:
m.global.cxRum.callfunc("reportError", { message: "no payment method on file", errorType: "PaymentError" })
m.global.cxRum.callfunc("reportError", "playlist arrived empty")Set a short errorType for each kind of failure, such as PaymentError, and
give thrown errors one too:
throw { message: "checkout failed", errorType: "PaymentError" }. An error's
data is capped at 2048 characters.
Ignore errors
ignoreErrors drops errors whose message matches. A string matches anywhere in
the message; a regular expression is written between slashes, with the flags
i, m, s and x:
coralogix_roku_initialize({
' ...
ignoreErrors: ["expected timeout", "/^Network error/i"]
}, screen.getGlobalNode())Crashes
The SDK reports crashes automatically on the next launch, with the exit code and the last 20 console lines before the crash. It doesn't report a crash followed by a device restart.
[!WARNING] The SDK sends these console lines to Coralogix. Keep tokens and personal data out of your console output.
| error_type | Meaning |
| --- | --- |
| BrightScriptCrash | The channel's code crashed |
| OutOfMemory | The channel ran out of memory |
| SystemKill | The OS closed the channel |
| Crash | Any other unexpected exit |
View tracking
Name each screen when it is shown. Records after it carry the view name:
m.global.cxRum.callfunc("startView", "Home")
m.global.cxRum.callfunc("startView", "Player") ' replaces Home
m.global.cxRum.callfunc("endView") ' clears the current viewLabels
setLabels adds labels, or changes existing ones, on every record that follows:
' initialized with labels: { tier: "free" }
m.global.cxRum.callfunc("setLabels", { tier: "premium", ab_test: "b" })
labels = m.global.cxRum.callfunc("getLabels") ' { tier: "premium", ab_test: "b" }Labels passed to a single log or error apply to that record only. Channel-wide
labels (initialize plus every setLabels) are capped at 512 characters: the
SDK ignores a setLabels call that would go over the cap, and warns on the
console. Keys are lowercased as described under Custom logs.
User context
Set the viewer after a sign-in, or clear it after a sign-out. Records from then on carry the new user:
m.global.cxRum.callfunc("setUserContext", {
user_id: "user-1",
user_name: "Jane",
user_email: "[email protected]",
user_metadata: { plan: "family" }
})
m.global.cxRum.callfunc("setUserContext", {}) ' signed outsetUserContext replaces the whole user: a field you don't pass is cleared.
Fields: user_id, user_name, user_email, account_id, account_name
(strings) and user_metadata (an object). The SDK refuses a call with an
unknown field and warns on the console.
Network requests
Use CxUrlTransfer instead of roUrlTransfer in the Task that makes your
requests. Its methods are the same, and the SDK reports every request with its
method, URL, status code, duration and size.
[!WARNING] Don't
callfuncfrom the render thread into the Task that makes requests while a request is in flight. Each side waits for the other and the channel hangs.
Load it in the Task. A component can only call functions from scripts it loads itself, so this step is required. In the Task's XML:
<script type="text/brightscript" uri="pkg:/source/roku_modules/coralogix_roku/instrumentations/network/CxUrlTransfer.brs" />Or, if the Task's code is in a BrighterScript (
.bs) file, import it there:import "pkg:/source/roku_modules/coralogix_roku/instrumentations/network/CxUrlTransfer.brs"Create it where you created
roUrlTransfer:ut = coralogix_roku_CxUrlTransfer(m.global) ut.SetUrl("https://api.example.com/catalog") body = ut.GetToString()For async requests, pass each
roUrlEventtoReportEvent:ut.SetMessagePort(port) ut.AsyncGetToString() msg = wait(0, port) if type(msg) = "roUrlEvent" ut.ReportEvent(msg) end if
To report a request made another way:
m.global.cxRum.callfunc("reportNetworkRequest", {
method: "GET", url: "https://api.example.com/catalog", statusCode: 200, durationMs: 184
})Optional fields: startTime (epoch ms), failureReason and
responseContentLength (bytes).
Ignore URLs
ignoreUrls drops network requests whose URL matches. A string must equal the
URL; a regular expression is written between slashes, with the flags i, m,
s and x:
coralogix_roku_initialize({
' ...
ignoreUrls: [
"https://api.example.com/health", ' this exact URL
"/^https:\/\/analytics\.example\.com\//" ' any URL on this host
]
}, screen.getGlobalNode())Traces
Add a W3C traceparent header to requests to your backends, so their traces
join the RUM session. allowedTracingUrls takes strings and regular
expressions, matched the same way as ignoreUrls:
coralogix_roku_initialize({
' ...
traceParentInHeader: {
enabled: true,
options: { allowedTracingUrls: ["/^https:\/\/api\.example\.com\//"] }
}
}, screen.getGlobalNode())The SDK adds the header to requests made with CxUrlTransfer. Without
allowedTracingUrls, every such request gets it, including third parties, except
URLs in ignoreUrls. If you set traceparent or tracestate yourself, the SDK
leaves your headers as they are.
Sessions
A session starts at initialize and ends after either of these:
| Ends after | Counts as activity |
| --- | --- |
| 2 hours of inactivity (set with sessionConfig.inactivityTimeoutMinutes, 15–240 minutes) | a key press on the remote, or reportUserActivity |
| 4 hours in total | nothing extends it |
The next record after a session ends starts a new session instead of being
dropped. The new session links to the one before it through prev_session.
Each new session is sampled again, so with a sessionSampleRate below 100 it
may not be recorded. An inactivityTimeoutMinutes of 240 matches the 4-hour
limit, so in practice only that limit ends a session.
Video playback produces no key presses, so a film longer than the inactivity timeout with no remote use starts a new session part-way through. To keep one session for the whole film, report activity while content plays, for example on each playback progress update:
m.global.cxRum.callfunc("reportUserActivity") ' returns the session idTo end the current session and start a new one, for example when the viewer signs out:
newSessionId = m.global.cxRum.callfunc("createNewSession")Shutdown
m.global.cxRum.callfunc("shutdown")Stops the SDK. It drops records not yet sent and reports nothing more.
Reading SDK state
Read-only calls, for showing or checking what the SDK reports:
| Call | Returns |
| --- | --- |
| getSessionId | The current session id, or "" once the session has ended and the next record has not yet started a new one |
| getConfig | The resolved configuration |
| getSessionContext | The session block sent with every record |
| getDeviceContext | The device block sent with every record |
| getDeviceState | The current network, memory and uptime readings |
To see each batch land, observe cxExportResult:
m.global.cxRum.observeField("cxExportResult", "onExportResult")
sub onExportResult(event as object)
result = event.getData()
print "post "; result.status; " — "; result.chars; " chars"
end substatus is the HTTP status of the upload; 0 or below means no response came
back at all. chars is the payload size in characters.
Troubleshooting
By default the SDK prints only warnings and errors to the device's debug
console: an option it rejected, a call it ignored, a batch that failed. Set
debug: true to also see what it does:
initialized, with the session id, when the SDK starts- every record, exactly as the SDK sends it
- each batch and its HTTP status (
post ok/post failed)
What the SDK sends to Coralogix doesn't change. Watch the console with:
nc <roku-ip> 8085initializereturnedfalse: the console names the wrong option.- No data in Coralogix: look for
post failed. A 4xx or 5xx status means Coralogix refused the batch; checkpublic_keyandcoralogixDomain. A status of0or below means no response came back at all (-28a timeout,-6a DNS failure): check the network, not the key. post okand still no data: Coralogix can accept a batch and still drop its records. Check the device's date and time,sessionSampleRate, andignoreUrlsandignoreErrors.- Nothing printed at all: check that
coralogix_roku_initializeruns afterscreen.show()and getsscreen.getGlobalNode(). - Finding a session in Coralogix: the
initializedline has its id, or callm.global.cxRum.callfunc("getSessionId")(empty once the session has ended).
License
Apache-2.0
