Usage Logs
This feature is experimental and may change in future releases.
Usage logs are available in v.26.1.5 or later.
Asprova My Schedule / WS server can record usage logs of events such as logins, logouts, and data access.
Enabling Usage Logs
Enabling usage logs may impact the performance of the server detrimentally.
Usage logging is disabled by default. To enable it, set the following in your server's config.json:
{
"enable_usage_log": true
}
Configuration
| Setting | Default | Description |
|---|---|---|
enable_usage_log | false | Enable or disable usage logging. |
usage_log_retention | "30d" | How long to keep usage log files before they are automatically deleted. |
Log Files
Usage log files are written to the logs directory inside your server's data directory (the user_file_directory).
By default, this is:
C:\ProgramData\Asprova\Asprova My Schedule\WS\logs
Each day produces a separate file named usage-YYYY-MM-DD.log. Files are rotated automatically and old files are removed based on the retention setting.
Each line in a log file is a JSON object containing:
- eventType — the type of event (see below).
- user — the user who triggered the event, if applicable.
userId— the user's ID.username— the user's name.
- req — information about the HTTP request that triggered the event, if applicable.
method— the HTTP method (e.g.POST).path— the request path (e.g./api/auth/login).ip— the client's IP address.
- details — additional context specific to the event.
- timestamp — when the event occurred.
Events
The following events are recorded:
| Event | Description |
|---|---|
ws:server:start | The server has started. |
ws:user:created | A user has been created. |
ws:user:login:success | A user logged in successfully. |
ws:user:login:failure | A login attempt failed. |
ws:user:logout | A user logged out. |
ws:user:removed | A user has been removed. |
my-schedule:license:ccu:added | A new concurrent user connection was accepted. |
my-schedule:license:ccu:max-reached | A connection was rejected because the concurrent user limit was reached. |
my-schedule:license:ccu:released-with-logout | A concurrent connection was released with user logging out. |
my-schedule:license:ccu:expired | A concurrent connection reserved by a user was released with expiration. |
my-schedule:license:maintenance-mode:entered | The server entered the maintenance mode. |
my-schedule:license:maintenance-mode:left | The server left the maintenance mode. |
my-schedule:view:accessed | A user accessed a view. |
my-schedule:view:uploaded | Data was uploaded to a view. |
ws:server:start
The server has started.
| Detail field | Description |
|---|---|
port | The port the server is listening on. |
https | Whether HTTPS is enabled. |
ws:user:created
A user has been created.
| Detail field | Description |
|---|---|
id | The new user's ID. |
username | The new user's name. |
type | The new user's login method. |
roles | The new user's roles. |
ws:user:login:success
A user logged in successfully. No additional detail fields.
ws:user:login:failure
A user attempted to log in but failed. Note that user field is not available for this event.
| Detail field | Description |
|---|---|
username | The username used in the attempt. |
reason | The reason why the attempt failed. |
ws:user:removed
A user has been removed. Note that user field points to the user who performed the removal.
| Detail field | Description |
|---|---|
id | The removed user's ID. |
username | The removed user's name. |
ws:user:logout
A user logged out. No additional detail fields.
my-schedule:license:ccu:added
A new concurrent user connection was accepted.
| Detail field | Description |
|---|---|
pool | The CCU pool the user has been added. |
currentCount | The current number of concurrent users after the connection was added. |
maxCount | The maximum number of concurrent users allowed for this plan. |
my-schedule:license:ccu:max-reached
A connection was rejected because the concurrent user limit was reached.
| Detail field | Description |
|---|---|
pool | The CCU pool the max has been reached. |
currentCount | The current number of concurrent users for this plan. |
maxCount | The maximum number of concurrent users allowed for this plan. |
my-schedule:license:ccu:released-with-logout
A concurrent connection count was released with logout. One event is emitted per pool.
| Detail field | Description |
|---|---|
pool | The CCU pool the user has been removed from. |
currentCount | The current number of concurrent users for this plan. |
maxCount | The maximum number of concurrent users allowed for this plan. |
my-schedule:license:ccu:expired
A concurrent connection was expired due to inactivity, and therefore, released. One event is emitted per pool.
| Detail field | Description |
|---|---|
pool | The CCU pool the user has been removed from. |
currentCount | The current number of concurrent users for this plan. |
maxCount | The maximum number of concurrent users allowed for this plan. |
my-schedule:license:maintenance-mode:entered
The server entered the maintenance mode due to insufficient license available. No additional detail fields.
my-schedule:license:maintenance-mode:left
The server left the maintenance mode. No additional detail fields.
my-schedule:view:accessed
A user accessed a view. This event is deduplicated — only one entry is logged per user, project, and view within a short time window.
| Detail field | Description |
|---|---|
projectId | The project ID. |
viewName | The name of the view (e.g. ResGantt, DispatchingView). |
my-schedule:view:uploaded
Data was uploaded to a view. This event is deduplicated — only one entry is logged per user, project, and view within a short time window.
| Detail field | Description |
|---|---|
projectId | The project ID. |
viewName | The name of the view (e.g. ResGantt, DispatchingView). |
Analyzing Logs with DuckDB
Usage log files are newline-delimited JSON (NDJSON), which DuckDB can query directly without any import step.
Setup
Open a terminal in the logs directory and launch DuckDB:
cd "C:\ProgramData\Asprova\Asprova My Schedule\WS\logs"
duckdb
Then create a view to avoid repeating the file path in every query. Use a glob pattern to include all log files:
CREATE VIEW usage_logs AS SELECT * FROM read_json_auto('usage-*.log', filename=true);
To query a single day instead:
CREATE VIEW usage_logs AS SELECT * FROM read_json_auto('usage-2026-04-10.log');
Counting events by type
SELECT eventType, count(*) AS count
FROM usage_logs
GROUP BY eventType
ORDER BY count DESC;
Listing login failures
SELECT timestamp, details.username, req.ip
FROM usage_logs
WHERE eventType = 'ws:user:login:failure'
ORDER BY timestamp;
Active users by view access
SELECT "user".username, details.projectId, details.viewName, count(*) AS accesses
FROM usage_logs
WHERE eventType = 'my-schedule:view:accessed'
GROUP BY "user".username, details.projectId, details.viewName
ORDER BY accesses DESC;
CCU usage over time
SELECT timestamp, eventType, details.pool, details.currentCount, details.maxCount
FROM usage_logs
WHERE eventType LIKE 'my-schedule:license:ccu:%'
ORDER BY timestamp;