Logging and monitoring

Enterprise Studio writes structured, machine-parseable logs to standard output.

Log output destination

All logs are written to stdout.

  • Binary: redirect stdout to a file and use an external tool such as logrotate. Enterprise Studio does not rotate logs internally.

  • Docker: logs are available through docker logs and forwarded to the configured Docker logging driver.

  • Kubernetes: container stdout is collected by the kubelet and available through kubectl logs. Use a log aggregator such as Fluentd, Fluent Bit, or Promtail to ship logs to a centralized system (for example Loki, Elasticsearch, or Splunk).

Retrieving logs

Because Enterprise Studio only writes to stdout, collecting logs means capturing the output of the process or container. The following examples follow the destinations above.

Binary

Redirect stdout and stderr to a file when you start Enterprise Studio.

On Linux:

./nes >> logs.txt 2>&1

On Windows:

.\nes.exe >> logs.txt 2>&1

Enterprise Studio does not rotate logs internally. Use an external tool such as logrotate to manage the log file.

Docker

Read the logs of a running container:

docker logs <CONTAINER-NAME>

Save them to a file. docker logs writes the container’s stdout to your stdout and its stderr to your stderr, so redirect both:

docker logs <CONTAINER-NAME> > logs.txt 2>&1

If you did not name the container with --name, find it with docker ps.

For production, configure a Docker logging driver so container stdout is shipped to your logging system.

Kubernetes

The examples below use the release name my-enterprise-studio from Deploy to Kubernetes.

Read the logs of all pods in the release:

kubectl logs -l app.kubernetes.io/instance=my-enterprise-studio

Follow the logs:

kubectl logs -f -l app.kubernetes.io/instance=my-enterprise-studio

Save the full log history to a file. By default kubectl logs truncates the output when selecting by label, so pass --tail=-1:

kubectl logs -l app.kubernetes.io/instance=my-enterprise-studio --tail=-1 > logs.txt

If the pod restarted, read the logs of the previous container to find out why it exited:

kubectl logs <POD-NAME> --previous

For production, use a cluster-level logging agent such as Fluentd, Fluent Bit, or Promtail to ship container stdout to a centralized system. Kubernetes does not store logs independently of the node or pod; see the Kubernetes logging architecture documentation.

Reading and filtering logs

In the default json format, each line is a self-contained JSON object, so you can filter logs with a tool such as jq.

Show only errors:

jq 'select(.level == "ERROR")' logs.txt

Condense a live log stream to one readable line per entry:

docker logs -f <CONTAINER-NAME> 2>&1 | jq -r '"\(.time) \(.level) \(.msg)"'

Follow all entries belonging to a single request using its request_id:

jq 'select(.request_id == "a1b2c3d4-e5f6-7890-abcd-ef1234567890")' logs.txt

Log format

Every JSON log entry contains the following top-level fields:

Field Description

time

ISO 8601 timestamp of the log event.

level

Log level: debug, info, warn, or error.

msg

Human-readable log message.

component

The internal subsystem that produced the entry (for example server, storageApi, config).

request_id

UUID generated per incoming HTTP request, useful for correlating log entries.

user

Object containing username, roles, and deployment of the authenticated user (present on request-scoped log entries).

error

Error details (present only when an error occurred).

Example JSON log entry:

{
  "time": "2025-06-10T14:32:01.456Z",
  "level": "INFO",
  "msg": "POST /api/shared-storage/v1/cypher 200 34ms",
  "component": "storageApi",
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "user": {
    "username": "[email protected]",
    "roles": ["editor"],
    "deployment": "production"
  }
}

Two output formats are available, controlled by the logs.format setting: json (default) produces structured JSON with one object per line; pretty produces colorized human-readable lines for local development. See Configuration → Logging for details.

At debug level, every Cypher® query and its parameters are logged, which significantly increases volume. Use info in production and switch to debug only for targeted troubleshooting.

Access logs

When assetStore.default.logs.requestLogs.enabled is true (the default), Enterprise Studio writes a structured log entry for every Storage API HTTP request.

Example access log entry:

{
  "time": "2025-06-10T14:35:12.789Z",
  "level": "INFO",
  "msg": "GET /api/shared-storage/v1/artifacts 200 12ms",
  "component": "storageApi",
  "request_id": "f7e6d5c4-b3a2-1098-fedc-ba9876543210",
  "protocol": "HTTP/1.1",
  "remote_ip": "10.0.0.5",
  "method": "GET",
  "uri": "/api/shared-storage/v1/artifacts",
  "status_code": 200,
  "latency": "12ms",
  "response_size": 4096,
  "user_agent": "Mozilla/5.0"
}

Compared to a standard log entry, access log entries add the protocol, remote_ip, method, uri, status_code, latency, response_size, and user_agent fields.

Since every request-scoped log entry includes the authenticated username, roles, and deployment, access logs provide an audit trail out of the box.

Health check endpoint

Enterprise Studio exposes a health check endpoint for use with load balancers and container orchestration platforms:

Method Path Response

GET

/health

200 OK with body OK.

This is a simple liveness check that confirms the HTTP server is running. It does not verify backend connectivity (for example to the storage database).

Storage API query profiling

The Storage API performs Cypher queries on the storage database. Enabling query profiling adds execution statistics to query logs, which can help diagnose slow operations.

To enable query profiling, set assetStore.default.queryProfiling.enabled to true. This setting is disabled by default. When enabled, Storage API data queries use the Cypher PROFILE keyword and the resulting metrics are added to log entries.

Query profiling logs are written at debug level. Set logs.level to debug to see them.

The following metrics are added to the profile_debug_stats object:

Metric Description

total_database_db_hits

Total database hits. See Cypher Manual → Database hits for more information.

records_returned

Number of records returned by the query.

documents_scanned

Database rows scanned for leaf operators in the query profile.

used_indexes

Indexes used by the query.

Example log entry with profiling enabled:

{
  "time": "2025-06-10T14:32:01.789Z",
  "level": "DEBUG",
  "msg": "Execute Cypher Query",
  "component": "storageApi",
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "profile_debug_stats": {
    "documents_scanned": "2",
    "records_returned": "1",
    "total_database_db_hits": "22",
    "used_indexes": [
      {"name": "used_index_1"},
      {"name": "used_index_2"}
    ]
  }
}

Generative AI requests

When generative AI is enabled, every proxied call to the LLM provider writes two info entries.

Generative AI request received is written before the request body is read, so a call rejected as too large or malformed still appears.

Generative AI request completed is written after the provider answers.

Example log entry:

{
  "time": "2025-06-10T14:41:22.104Z",
  "level": "INFO",
  "msg": "Generative AI request completed",
  "request_id": "c3d4e5f6-a7b8-9012-cdef-3456789abcde",
  "referringClient": "upx:dashboards",
  "durationMs": 2841,
  "promptTokens": 1204,
  "completionTokens": 733,
  "totalTokens": 1937
}

The request_id links the two entries for a single call.

A completion that the provider cut short still returns 200 OK with truncated content, and is logged as a warning:

{
  "time": "2025-06-10T14:41:22.100Z",
  "level": "WARN",
  "msg": "LLM provider did not finish the completion",
  "finishReason": "length",
  "maxCompletionTokens": 16384,
  "completionTokens": 16384
}

A finishReason of length means settings.maxCompletionTokens is too low for that feature.

At debug level, the full provider request and response are logged, including prompt content. Bodies are truncated at 8 KiB, and truncated entries carry "bodyTruncated": true. Provider error responses are logged at debug only. Use this for targeted troubleshooting, not in production.

Redaction

Passwords, SSO client secrets, and the generative AI provider API key are replaced with [REDACTED] in log output. The values of generativeAi.chatCompletionProvider.default.upstream.headers are redacted as well; the header names remain visible so you can confirm which headers are sent. Usernames, IP addresses, user agent strings, and Cypher query parameters (at debug level) are not redacted. To remove these from stored logs, mask them in your log pipeline before long-term storage.

Error responses

The Storage API returns error responses using the RFC 9457 problem details format. Each response contains a type (machine-readable identifier), title (human-readable summary), and optionally detail and errors (for batch failures).

{
  "type": "not-found",
  "title": "resource 'dashboard/abc-123' not found"
}

The type field distinguishes errors that share the same HTTP status code — for example not-found vs not-found-parent (both 404), or validation-error (422) vs json-syntax-error (400). These values are stable and can be used in alerting rules. The same type value appears in the corresponding structured log entry.