Logs and log levels
Where Bonita Process Designer writes its logs in each deployment mode, how to change the log level, what a log line contains, and how request correlation ids let you trace a single request end to end.
Bonita Process Designer writes human-readable logs to the standard output of its process. The standalone distribution also writes them to a rotating file, ready to collect, with nothing to configure — see Standalone. In Docker, the output of the app container is the log to collect; the container also keeps a bounded copy of it inside the container — see Docker.
Two things make these logs useful when diagnosing a problem:
-
every log line of a given HTTP request carries the same correlation id, so you can follow one request across all the lines it produced;
-
that same id is shown to the end user as a Support ID when a server error occurs, which lets you find the exact failure in the logs from a user’s report. See Get support.
Where the logs are
The answer depends on how you installed the product.
Docker
Each container logs to its own standard output, collected by Docker. Follow them with docker compose logs, from the directory of docker-compose.yml:
# Everything, followed live
docker compose logs -f
# One service at a time
docker compose logs -f app # application log (see the format below)
docker compose logs -f caddy # TLS reverse proxy
docker compose logs -f postgres # database
To export the application log to a file for a support request:
docker compose logs --no-color --timestamps app > app.log
In Windows PowerShell 5.1, > writes the file in UTF-16. Write it in UTF-8 instead:
docker compose logs --no-color --timestamps app | Out-File -Encoding utf8 app.log
The app service holds the application log — it is the one to collect first. With the plaintext HTTP opt-out, add -f docker-compose.http.yml to each command.
The container output is the log of record, and the shipped docker-compose.yml caps its size — see Rotation and retention. The app container also writes a copy of its log to /var/log/process-designer/app.log, inside the container. That copy is what the diagnostic archive reads, because an application cannot read its own container output. It is bounded to 100 MB, rotated like the standalone file, and lost when the container is recreated, like the container output itself. To turn it off, add LOGGING_FILE_NAME= (an empty value) to the environment: of the app service.
Standalone
The standalone distribution is a single process: one Java application serves both the REST API and the editor UI. It writes its log to two places at once — the console where you started it, and a file:
logs/process-designer.log
relative to the directory holding app.jar. The start script prints the exact path when it launches. Nothing needs configuring, and the file is bounded: see Rotation and retention.
This file is what to collect for a support request. Send the current file and the archives covering the incident:
# Everything the retention window still holds
tar czf process-designer-logs.tar.gz logs/
To follow it live:
tail -f logs/process-designer.log
On Windows, where there is no tail, PowerShell does the same:
Get-Content -Wait logs\process-designer.log
When you run the application as a systemd service, the console half of that output goes to the journal (journalctl -u process-designer). The file is written either way, so there is no need to capture the console separately.
|
Do not redirect the console output into the same |
Log levels
Two environment variables control verbosity. Both default to INFO.
| Variable | Scope | Default |
|---|---|---|
|
The application’s own code. Raise this first when diagnosing a problem — it adds the detail that is deliberately kept out of |
|
|
Everything else: the framework, the database driver, and the other libraries. Raise this only when the problem looks like it sits below the application — for example a connection or startup failure. |
|
Refused requests do not need DEBUG: a rejected permission check, an expired license or an exceeded rate limit is logged at WARN, so it is already in the log at the default level. See Notable warnings.
|
A few framework loggers that are unusually noisy — or that would write sensitive data — are pinned to their own level, independently of |
Accepted values are ERROR, WARN, INFO, DEBUG and TRACE.
|
These two variables are read at startup. Restart the application after changing either of them. If a restart is not acceptable — a production instance you cannot interrupt — a level can also be changed on a running instance, temporarily. See Changing a level without restarting. |
Setting the level in Docker
The shipped docker-compose.yml passes the level variables through to the container from your environment, so either export them for a single run:
LOG_LEVEL=DEBUG docker compose up -d
or add them to the .env file next to docker-compose.yml to make the change persist:
LOG_LEVEL=DEBUG
LOG_LEVEL_ROOT=WARN
Setting LOG_LEVEL=DEBUG together with LOG_LEVEL_ROOT=WARN is the usual combination: full application detail, quiet frameworks.
docker-compose.http.yml, the plaintext HTTP opt-out, does not pass them: add them to the environment: of its app service, for example - LOG_LEVEL=DEBUG, then run docker compose -f docker-compose.http.yml up -d.
Setting the level in the standalone distribution
Add the variable to config.env and restart:
LOG_LEVEL=DEBUG
|
Remember to set the level back to |
Changing a level without restarting
An Admin can raise or lower the verbosity of a running instance. The change takes effect immediately and lasts until the next restart, which makes it the right tool for "turn it up, reproduce the problem, turn it back down" on an instance you cannot interrupt.
Finding the right URL
The management endpoints are served by the application under /actuator. Which URL reaches them depends on how you installed the product:
| How you reach the product | Base URL |
|---|---|
Docker, through the bundled TLS proxy ( |
|
Docker, with the plaintext HTTP opt-out ( |
|
Standalone |
|
The recipes below refer to the part before /actuator as $API, so the same commands work in every mode. Set it once, to the value matching your own installation — the example is a Docker install reached through the TLS proxy on the local host:
API=https://localhost
|
Behind the bundled TLS proxy, the certificate is self-signed — issued by the proxy’s own internal certificate authority.
That call proves the certificate is accepted, but it still answers Trusting that root certificate on the client machines also removes the browser’s "not trusted" warning. Neither flag is needed once you have replaced the bundled certificate with one signed by a CA your hosts already trust — and neither applies at all when you reach the application over plain HTTP, where you can drop the |
Signing in from the command line
The endpoints need an Admin session, carried by the connect.sid cookie. There are two ways to get one.
From the browser — sign in as an Admin, open the developer tools, and read the value of connect.sid under Application → Cookies. Then substitute -b 'connect.sid=<value>' for -b cookies.txt in the commands that follow.
From the command line — sign in with curl and let it keep the cookie in a jar:
curl -k -c cookies.txt \
-H 'Content-Type: application/json' \
-d '{"username":"<admin-user>","password":"<password>"}' \
"$API/auth/login"
Then pass -b cookies.txt on each subsequent call, as below.
|
A password on a command line ends up in your shell history. Prefer the browser route on a shared machine, and delete |
Changing the level
# Raise the application's own log to DEBUG
curl -k -b cookies.txt \
-H 'Content-Type: application/json' \
-d '{"configuredLevel":"DEBUG"}' \
"$API/actuator/loggers/com.bonitasoft.processdesigner"
# Check what a logger is set to
curl -k -b cookies.txt \
"$API/actuator/loggers/com.bonitasoft.processdesigner"
# Put it back
curl -k -b cookies.txt \
-H 'Content-Type: application/json' \
-d '{"configuredLevel":"INFO"}' \
"$API/actuator/loggers/com.bonitasoft.processdesigner"
Unlike LOG_LEVEL, this also lets you target one logger rather than the whole application package — useful for turning up a single component without the volume of a full DEBUG run.
|
The change is not persisted. After a restart the instance returns to whatever |
|
These management endpoints require the Admin role — no part of them is available anonymously or to a Reader or Creator. They are the only way to change a running instance’s behavior over HTTP, so treat Admin credentials accordingly. See Roles and permissions. |
Two companion endpoints answer the other questions a support request usually starts with. They take the same base URL and the same Admin session:
| Endpoint | What it tells you |
|---|---|
|
The exact product version running, which is more reliable than reading the footer of a page that may be cached. |
|
Whether the application and its database connection are up. |
What a log line looks like
2026-07-15T10:00:00.000+00:00 INFO [my-trace-42] [http-nio-8080-exec-1] c.b.p.web.RequestLoggingFilter : GET /diagrams -> 200 (12 ms) user=walter
Reading left to right: the timestamp with its UTC offset, the level, the correlation id in square brackets, the thread, the shortened class name, then the message. Lines that do not belong to a request — startup, scheduled tasks — show an empty pair of brackets.
The access log
The application writes one INFO line per HTTP request once it completes, with the method, path, response status, duration, and the authenticated user (anonymous when the request carried no session). That single line is often enough to answer "did the request reach the server, and what did it return?".
The query string is not recorded at INFO, because query parameters can carry sensitive values. Set LOG_LEVEL=DEBUG to have it appended.
Browser OPTIONS preflight requests are not logged.
Following one request: the correlation id
Every request is assigned an id when it arrives. That id is:
-
written on every log line produced while handling the request, in square brackets;
-
returned to the client on the
X-Request-Idresponse header; -
shown to the user as the Support ID when the server returns an error (see Get support).
So a single value ties together the user’s report, the browser’s network trace, and the server logs. Given an id, find everything the server did:
# Docker
docker compose logs --no-color app | grep '\[a1b2c3d4-...\]'
# Standalone — search the archives too, the id may predate the current file
zgrep -h '\[a1b2c3d4-...\]' logs/process-designer.log*
On Windows there is no zgrep, so expand each gzipped archive before searching it:
$id = '[a1b2c3d4-...]'
# The current file
Select-String -Path logs\process-designer.log -SimpleMatch $id
# The archives, one at a time
Get-ChildItem logs\*.gz | ForEach-Object {
$archive = $_.Name
$tmp = [System.IO.Path]::GetTempFileName()
$in = [System.IO.File]::OpenRead($_.FullName)
$gz = New-Object System.IO.Compression.GZipStream($in, [System.IO.Compression.CompressionMode]::Decompress)
$out = [System.IO.File]::Create($tmp)
$gz.CopyTo($out)
$out.Close(); $gz.Close(); $in.Close()
Select-String -Path $tmp -SimpleMatch $id | ForEach-Object { "${archive}: $($_.Line)" }
Remove-Item $tmp
}
Real-time collaboration
Collaborative editing does not use ordinary HTTP requests, so its log lines carry a different kind of id in the same place — the editing session and the user who owns it:
2026-08-04T13:00:01.000+02:00 WARN [ws-k3n9x1qz/walter] ... READER_OP_REJECTED
Read it as ws-<editing session>/<user>. That makes a collaboration problem searchable from either end: by the session, to follow one editor’s whole sitting, or by the user name, to find every session they opened.
|
An editing session’s id is independent of the ids on that user’s ordinary requests — there is no value linking the two. To investigate a collaboration problem, search by the user name and the time of the incident. |
You can also supply your own id to trace a call you make yourself. It is accepted when it is at most 64 characters of letters, digits, dot, underscore or hyphen; anything else is replaced by a generated id.
curl -k -H "X-Request-Id: my-trace-42" "$API/diagrams"
The response echoes X-Request-Id: my-trace-42, and the server’s log lines for that call carry [my-trace-42].
|
Requests rejected before authentication — an expired license, a payload over the size limit, an exceeded AI rate limit — are logged with a correlation id too, so those failures are traceable as well. |
What is never written to the logs
The logs are designed to be safe to send to Support. They never contain:
-
passwords, API keys, license file contents, or any other secret;
-
session identifiers or cookie values;
-
request or response bodies — including diagram content, BPMN payloads, and the prompts submitted to AI generation;
-
query strings, unless you deliberately raise the level to
DEBUG.
Identifiers are recorded instead of content: a path, a user name, a diagram id, a job id. Values that come from a client are stripped of control characters before being written, so a crafted request cannot forge or split a log line.
|
This guarantee covers the application log. It does not extend to a database dump or a diagram export, which do contain your process content — treat those separately when sharing files. |
The proxy log
The list above is about the application log — the one the app container writes and the one logs/process-designer.log holds. It records no network identifier at all: no client IP address, no browser user agent.
In the Docker distribution, the caddy proxy keeps a log of its own. The shipped Caddyfile turns on no access log, so that log holds the proxy’s own events, such as certificate management and errors. An error entry can describe the request that failed: the client address, the requested address with its query string, and request headers such as the browser’s user agent. The collection recipes on this page export the app service only: review the caddy log before you share it.
Notable warnings
WARN lines are rare by design: the application logs one when it refuses something, never on the normal path. Seeing them is the quickest way to spot a misconfiguration:
| Rejection | What it means |
|---|---|
License refused ( |
The license is missing, invalid, or expired. See Manage the license. |
AI rate limit ( |
The caller exceeded the AI request rate. The line records the user and how long to wait. |
Request too large ( |
The payload exceeded the configured cap. The line records the size and the limit. |
Access denied ( |
A user attempted something their role does not allow, or credentials were wrong. See Roles and permissions. |
Conflict ( |
Two operations collided on the same data — for example a duplicate name. |
An ERROR line with a stack trace means an unexpected server failure. That is the case where the user is shown a Support ID, and the one worth reporting.
Rotation and retention
- Standalone
-
The log file rotates on its own — no
logrotateconfiguration needed.Limit Default Meaning Size per file
10 MB
The current file rolls over when it reaches this, and also once a day. Keeping it small is deliberate: this is the file you open in an editor or
grepduring an incident.History
30 days
Archives older than this are deleted. Chosen so a problem reported a few days late can still be investigated.
Total size
1 GB
Once the archives reach this, the oldest are deleted first, whatever their age.
Archives sit next to the current file, gzipped, one per rollover:
logs/process-designer.log (1) logs/process-designer.log.2026-08-03.0.gz (2) logs/process-designer.log.2026-08-02.0.gz1 the current file 2 archives, oldest deleted first The total-size limit is applied at each rollover, so the directory can briefly hold slightly more than 1 GB — about one archive’s worth. Provision for the limit as a bound, not as an exact ceiling.
To change any of them, set the matching variable in
config.envand restart:LOGGING_LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE=10MB LOGGING_LOGBACK_ROLLINGPOLICY_MAX_HISTORY=30 LOGGING_LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP=1GB - Docker
-
The container output is rotated by the Docker logging driver. The shipped
docker-compose.ymlanddocker-compose.http.ymlalready cap it, service by service:Service logging:optionsTotal appmax-size: "50m",max-file: "10"500 MB
caddy,postgresmax-size: "10m",max-file: "3"30 MB
The
appservice, as shipped:services: app: logging: driver: json-file options: max-size: "50m" max-file: "10"Read those two options carefully:
max-sizebounds each file, andmax-filecounts files, not days. Theappcap is therefore 500 MB (10 x 50 MB) with no time limit — how many days of history that buys depends entirely on your request volume and log level, and nothing expires on age. Docker’s rotation has no equivalent of the standalonemax-history. Once the cap is reached, the oldest lines are discarded. If you ship container logs to your own aggregator, change thelogging:settings of the services and set the retention there.The copy of the log kept inside the
appcontainer rotates like the standalone file — at 10 MB and once a day, 30 days of gzipped archives — with a total cap of 100 MB instead of 1 GB.
|
Raising the level to |
See also
-
Get support — what to collect before opening a support request.
-
Configuration reference — every environment variable, including the two on this page.