Logging
Zuplo provides real-time logging out of the box. If you would like your logs to be sent to your own logging service, you can enable one of Zuplo's logging plugins.
To configure your logging, you need to create a zuplo.runtime.ts file in the
modules. The examples below show the content of the file with each of the
different logging plugins.
Viewing logs in the portal
Every Zuplo project has a built-in log viewer in the Zuplo Portal that shows
request logs and messages logged with context.log. Open your
project, go to Observability →
Logs, and use the Environment filter to choose the environment to view.
How far back you can search depends on your plan. See
Data retention.
The viewer supports time-range presets, drag-to-zoom on the histogram, free-text search, and filters for severity, status, method, and route. Live mode streams new entries as they arrive and pauses automatically, for example when you select a request to inspect it.
Each request row shows the path the client called, such as /users/42. Hover
the path to see the full URL and the route it matched, such as /users/:id.
Select a request to open its details, which list:
- URL: the full URL of the request, including the query string.
- Route Path: the route in your OpenAPI file that handled the request.
- Properties: any custom log properties your code set for the request.
When your code calls another route with
context.invokeRoute, the
called route gets a log row of its own with the same request ID. The viewer
nests that row under the request that made the call and marks it Subrequest,
so a request and its sub-requests read as one group even when their status codes
differ.
The log viewer shows the full URL of each request, including its query string. Don't pass secrets such as API keys or tokens as query parameters; send them in headers instead.
Logs are also available at the account scope, so you can debug across every project in an account in one place. Open Observability → Logs at the account level, then use the Project filter in the sidebar to narrow the view to a single project. The account view includes logs only from projects where your role lets you view logs.
Log Integrations
Zuplo offers out-of-the box integrations with many common logging vendors. For instructions on how to configure logging, see the documentation for each plugin:
- AWS CloudWatch
- Datadog
- Dynatrace
- New Relic
- Google Cloud Logging
- Loki
- Splunk
- Sumo Logic
- VMware Log Insight
Not seeing the logging plugin you need? Reach out to support@zuplo.com and we'd be happy to work with you to add a new logging plugin.
Custom Logging
In addition to the logging plugins, you can also create your own custom logging plugin. For more information, see the Custom Logging Plugin documentation.
Log Fields
Below is a list of the default fields that are sent with log messages. Note that
the names of these fields may differ depending on your logger as we follow the
conventions of each log service. So environmentType may be environmentType,
environment_type, or environment-type. See the specific log plugin for
details.
severity: The log level of the message. Values aredebug,info,warn,errorrequestId: The value of thezp-ridheader. This is used for tracing issues across Zuplo systems.atomicCounter: This is a counter that indicates log ordering. Because of the shared nature of the edge environments the clock doesn't incriment unless an I/O operation is performed. As such, you may notice that you have several messages with the same timestamp. You can use the value of this counter to determine order. This value will be an integer between 0 and the max integer value. It will cycle back to 0 when the maximum is reached. This number isn't persistent across restarts or shared across environments.environment: This is the name of your Zuplo environment. This will be the same as your Zuplo subdomain. for example if your Zuplo URL ishttps://silver-lemming-main-b0cef33.zuplo.app, the environment issilver-lemming-main-b0cef33environmentType: This indicates where your environment is running. Possible values are:edge: Environments deployed to our 300+ edge locationsworking-copy: Environments deployed to your single-instance dev serverlocal: When running with Zuplo local development
environmentStage: This indicates the deployment stage of your environment. Possible values are:production: Environments deployed from your default git branchpreview: Environments deployed from any other git branchworking-copy: Environments deployed to your single-instance dev serverlocal: When running with Zuplo local development
loggingId: This string is a unique identifier that combines your environment name, branch name, and project name into one string. We advise against using this value and instead recommend using theenvironmentandenvironmentStagevalues for filtering. This value will likely be deprecated in future releases.buildId: A UUID representing the unique build of your API. This value changes every time a new version of your API is built and deployed.logSource: Whether the log originated from a request (request) or from outside of a request (runtime)rayId: The value of the network provider request ID (that is Cloudflare Ray ID). This value is used internally to coordinate log event in Zuplo to log events in the environment where your API runs. It's provided in your logs for potential troubleshooting. Normally, it's recommended to rely on therequestIdfor tracing.
Custom Log Properties
In addition to the default fields, you can add custom properties to a request
with the context.log.setLogProperties! method. Use them for values you want to
see on the request, such as a tenant ID, a feature flag, or an experiment
variant.
Code
Custom log properties show up in two places:
- Your logging plugins: every log message for the request logged after the
call includes the properties. They're exported in a format native to the
plugin you use. For example, the OpenTelemetry plugin includes them in the
attributesfield of the log record. - The log viewer in the Zuplo Portal: the request's details list the properties in the Properties section. See Viewing logs in the portal.
Custom log properties work as follows:
- Properties apply only to the request that sets them.
- Values can be strings, numbers, or booleans.
- Each call merges into the properties already set for the request. If a property already exists, it's overwritten.
- A per-request property overrides a static field with the same name from your
logging plugin's
fieldsoption, for plugins that have one. - A route called with
context.invokeRoutestarts with no custom log properties of its own, and its properties appear on its own row in the log viewer, not on the calling request.
To keep requests small in the log viewer, the following limits apply there. A property that breaks a limit is left out of the log viewer, or, for a value that's too long, truncated. Log messages sent to your logging plugins aren't limited this way.
| Limit | Value |
|---|---|
| Properties per request | 64 |
| Key length | 128 characters |
| Value length | 2,048 characters |
The ! in setLogProperties! is a TypeScript non-null assertion: the method is
typed as optional on the logger, but it's always available on context.log.
Performance Under High Load
Logging costs compute and memory on every request. That's rarely noticeable on a single response, but on an API running at a high request rate it shows up as lower peak throughput and higher tail latency.
For high-traffic APIs, and before a load test:
- Log the events you'd act on rather than everything that happens. Verbose
debuglogging on a hot path is the usual culprit. - Keep custom log properties small. Identifiers cost far less than whole request or response bodies.
- Add logging plugins deliberately. Each one is another copy of every record.
OpenTelemetry tracing carries a similar cost and supports sampling to bring it down.