Running Lens in production

On this page

Running Lens in production

The bar is a development tool. The console can run on a live server if you set it up with care. This guide gives a safe starting point.

What changes in production

When the console is on, Lens collects every request on the server, not only those from allowed callers, so the console can show real traffic. Only people who pass the access rule and sign in can read it. That makes three choices important: who may reach it, what is collected, and what the bar does.

A safe starting point

{
	"modules": {
		"bxLens": {
			"settings": {
				"bar": { "enabled": false },
				"console": {
					"enabled": true,
					"password": "bxsecret:AbCdEf123...==",
					"viewerPassword": "bxsecret:GhIjKl456...==",
					"requireHttps": true,
					"access": [ "10.8.0.0/24" ],
					"sessionMinutes": 15,
					"actions": false,
					"readOnly": true,
					"allowHeapDump": false
				},
				"collect": { "level": "light" },
				"access": {
					"allowedHosts": [ "ops.example.com" ],
					"trustProxyHeader": true,
					"proxyHeader": "X-Forwarded-For",
					"proxyPeers": [ "10.0.1.5" ]
				},
				"store": { "dir": "/var/lib/boxlang/lens-data" },
				"ai": { "enabled": false },
				"history": { "maxRequests": 100 }
			}
		}
	}
}

readOnly: true and actions: false suit a console that is only for looking. Take them out when your operators need to change settings or run tasks from the console.

Step by step

  1. Encrypt the passwords. Run boxlang generatesecret "a long random password" and paste the bxsecret: value into console.password. Do the same, with another password, for console.viewerPassword. Pin the seed with BOXLANG_SECURITY_SECRETSEED if several machines share one config. Never commit a plain text password.
  2. Give people the right role. Operators who only look use the viewer password. A viewer cannot change anything, cannot download dumps, logs or the bundle, and does not get the Logs, Environment, System and Threads pages, but can still read request data and queries, so keep the password private. See Roles.
  3. Limit who can reach it. Set console.access to a VPN range or a short list of IPs and CIDRs. Keep "local" if you tunnel in with SSH. Avoid "all": it works, but Lens logs a warning. Add access.allowedHosts for the host name you use (a local rule otherwise accepts only localhost, 127.0.0.1 and [::1] as Host names), and access.requireHeader if a proxy can add a secret header.
  4. Use HTTPS and require it. Set console.requireHttps to true. Terminate TLS in front of the runtime and make sure the runtime sees the request as secure: either it reports HTTPS itself, or your proxy sends X-Forwarded-Proto: https. Check the cookie in your browser tools.
  5. Say which proxies to trust. Behind a proxy or load balancer, set access.proxyPeers to the addresses of your proxies, not the whole private network, so only they can name the client address. Without a proxy, set access.trustProxyHeader to false. See Behind a proxy.
  6. Collect lightly. collect.level: "light" skips request headers, bound query parameters, the caller (template and line) of queries, HTTP calls and transactions, the BoxLang frames of exceptions, scope contents, Java stack traces, and the functions, logs, scopes and bifs collectors. SQL text, timings and counts stay. It is the default. Non HTML requests are not kept in the history (history.trackNonHtml), and the history is built lazily, so a request nobody opens costs no snapshot. Use full only on a machine you control.
  7. Turn the bar off. Leave bar.enabled false. Developers do not need a strip injected into live pages.
  8. Decide on changes. Set console.actions to false if nobody should run, pause or reload tasks from here (these actions also need BoxLang+). Set console.readOnly to true if nobody should change anything from the console. The pages stay readable.
  9. Keep heap dumps off. Heap dumps need BoxLang+ or a trial. Leave console.allowHeapDump at false. A heap dump holds every secret in memory. Turn it on for the time you need it, then off. See Heap dumps.
  10. Decide on AI. Leave ai.enabled false, or use a local provider so prompts stay in your network. See AI data flow.
  11. Put the store on a volume. With BoxLang+ or a trial, errors and reports are saved to store.dir. In a container, mount a volume there, or the data is lost when the container is replaced (Lens logs a warning when it sees the folder on the container's own file system). Give each server a stable name with store.instanceFolder or LENS_INSTANCE. Set store.retentionDays and store.maxMB to what you want to keep. See The store.
  12. Hide what you do not need. tabs.hide removes pages, for example ["threads", "designer", "environment"].
  13. Size the history. history.maxRequests is the memory you spend. Each request holds its full snapshot in memory. On Free the history is capped at 25.
  14. Watch the audit log. bxlens-audit.log in the logs directory records logins, failed logins, denied attempts and every change. Read it on the Logs page or ship it with your other logs.

Work off the request thread

A collector never blocks and never throws into a request, and heavy aggregation is async. When a request ends, Lens closes its spans, takes its status and, if the bar shows, builds the payload for the page. Everything else is queued for one daemon thread (bxlens-worker): the query statistics, the error groups, the reports, the history entry and the audit log lines. Issue analysis is not done at all until the console opens a request.

  • The queue is bounded (async.queueSize, default 2000). When it is full the new task is dropped and counted. A request never waits for it.
  • A task that fails is counted and forgotten. The worker keeps going.
  • Tasks run in order. Pages of the console and the data BIFs wait (up to a second) for the queue to catch up before they read, so they always include the request that just ended.
  • At shutdown the queue drains (up to three seconds) before the statistics are saved.
  • The depth, the number of finished tasks and the dropped count are on the console Overview and System pages and in lensDiagnostics(). A dropped count that keeps growing means the server is busier than the worker. Raise async.queueSize or turn collection down with collect.level.
  • async.enabled: false does the work in the request again.

The files of the bar

A page that shows the bar gets a block of about a kilobyte, not the whole bar. The styles, the script, Alpine.js, the markup and the icons are five files under /~bxlens/index.bxm/assets/, with a content hash in the URL. A browser (or a CDN or proxy) keeps each for a year (Cache-Control: public, max-age=31536000, immutable) and fetches the new ones by itself after an upgrade. They hold no request data and no secret. See Bar files for the headers, who can fetch them and the Content-Security-Policy the bar needs.

If you proxy to BoxLang with a path filter, allow /~bxlens/index.bxm/assets/* for the people who see the bar, not only for the console.

The console URL

/~bxlens/index.bxm always works. Lens also rewrites /~bxlens and /~bxlens/ (with the trailing slash) to it, in onWebExecutorRequest. Nothing else in the public folder becomes reachable that way.

On MiniServer the request must also pass the pass predicate, or MiniServer serves it as a static path and never hands it to BoxLang. Add the console path to the default predicate. Set the environment variable BOXLANG_PASS_PREDICATE to this exact value:

regex( '^(/.+?\.cfml|/.+?\.cf[cms]|.+?\.bx[ms]{0,1})(/.*)?$' ) or regex( '^/~bxlens/?$' )

or set the same string as passPredicate in miniserver.json (in JSON, write each backslash twice). The first regex is the default MiniServer predicate and the second one lets the console path through. The harness does this in harness/start.sh.

MiniServer welcome files do not apply to module mappings, and the list of welcome files is not configurable in core. That is why Lens does the rewrite itself.

On CommandBox or a servlet container this was not tested. The trailing slash form works only if the path reaches the BoxLang servlet, which depends on the server's own mappings. Use /~bxlens/index.bxm there.

Check it

  • Request /~bxlens/index.bxm from a machine that is not allowed. You get a plain 404.
  • Request it over plain HTTP from an allowed non-loopback machine. You get a 403 with HTTPS is required for the console.
  • Request it from an allowed machine over HTTPS. You see the login page.
  • Sign in with the viewer password and try to change a setting. The fields are disabled, and a direct call gets a 403.
  • The Settings page shows the passwords as set, collect.level as light and console.readOnly as true.
  • The log shows bx-lens ... active: bar=false, console=true, collect=light.
  • After a restart, the Reports page still shows "Since first install" (needs BoxLang+ or a trial).

Know the limits

The console is for one server. It keeps recent requests, query statistics and in-flight data in memory and loses them at a restart. With BoxLang+ or a trial, errors and reports survive restarts. For several nodes, history over time and alerts, use BX Insights.

See also Security.