Logs
Stream live logs from devices, or query historical logs over a time range. Logs are emitted by devices via the Device SDK Logs module at three severity levels: info, warn, and error.
Access via app.log.
API
stream()
Subscribe to live logs from a device. Optionally filter by severity level.
- JavaScript
- Python
await app.log.stream(params)await app.log.stream(params)| Parameter | Type | Description |
|---|---|---|
device_ident | string | The device identifier |
levels | string[] or "*" | Optional. Subset of ["info", "warn", "error"], or "*" for all. Defaults to all. |
callback | function | Called with each incoming log entry |
The callback receives an object with level, data, and timestamp:
| Field | Type | Description |
|---|---|---|
level | string | One of "info", "warn", "error" |
data | string | The formatted log message |
timestamp | number | Unix milliseconds, set by the device |
- JavaScript
- Python
// all levels
await app.log.stream({
device_ident: "sensor_01",
callback: (entry) => {
console.log(`[${entry.level}] ${entry.data}`);
},
});
// errors only
await app.log.stream({
device_ident: "sensor_01",
levels: ["error"],
callback: (entry) => {
console.error(entry.data);
},
});
# all levels
await app.log.stream({
"device_ident": "sensor_01",
"callback": lambda entry: print(f"[{entry['level']}] {entry['data']}"),
})
# errors only
await app.log.stream({
"device_ident": "sensor_01",
"levels": ["error"],
"callback": lambda entry: print(entry["data"]),
})
You can call stream() multiple times for the same device with different level filters and callbacks. Each call creates an independent subscription.
off()
Tear down all live log subscriptions for a device.
- JavaScript
- Python
await app.log.off(params)await app.log.off(params)| Parameter | Type | Description |
|---|---|---|
device_ident | string | The device identifier |
off() removes every active subscription for the given device. There is no per-level removal. To change a level filter, call off() and then stream() again with the new levels.
- JavaScript
- Python
await app.log.off({ device_ident: "sensor_01" });
await app.log.off({"device_ident": "sensor_01"})
history()
Query historical logs over a time range.
- JavaScript
- Python
await app.log.history(params)await app.log.history(params)| Parameter | Type | Description |
|---|---|---|
device_ident | string | The device identifier |
levels | string[] | Optional. Subset of ["info", "warn", "error"]. Defaults to all three. |
start | string | Start time (ISO 8601, UTC) |
end | string | End time (ISO 8601, UTC) |
interval | string | Optional. Bucket size as a Flux duration ("1h", "5m"). Pair with aggregate_fn. |
aggregate_fn | "count" | Optional. "count" is the only valid value. |
Returns an object keyed by level, each containing an array of entries.
- JavaScript
- Python
const logs = await app.log.history({
device_ident: "sensor_01",
levels: ["error"],
start: "2026-04-28T00:00:00.000Z",
end: "2026-04-29T00:00:00.000Z",
});
logs.error.forEach((entry) => {
console.error(`${entry.timestamp}: ${entry.value}`);
});
logs = await app.log.history({
"device_ident": "sensor_01",
"levels": ["error"],
"start": "2026-04-28T00:00:00.000Z",
"end": "2026-04-29T00:00:00.000Z",
})
for entry in logs["error"]:
print(f"{entry['timestamp']}: {entry['value']}")
Example response:
{
"info": [
{ "value": "Boot complete: firmware=1.2.0", "timestamp": 1774690200000 }
],
"warn": [
{ "value": "Retrying transport publish", "timestamp": 1774690260000 }
],
"error": [
{ "value": "Sensor read failed: EIO", "timestamp": 1774690320000 }
]
}
Levels you did not request are still included in the response object as empty arrays.
Counting logs over time
Use interval and aggregate_fn: "count" to bucket logs into time intervals. This is useful for charting error rates or detecting spikes.
- JavaScript
- Python
const hourlyErrors = await app.log.history({
device_ident: "sensor_01",
levels: ["error"],
start: "2026-04-28T00:00:00.000Z",
end: "2026-04-29T00:00:00.000Z",
interval: "1h",
aggregate_fn: "count",
});
hourly_errors = await app.log.history({
"device_ident": "sensor_01",
"levels": ["error"],
"start": "2026-04-28T00:00:00.000Z",
"end": "2026-04-29T00:00:00.000Z",
"interval": "1h",
"aggregate_fn": "count",
})
Log retention depends on your plan. Queries outside your retention window will return empty arrays.