Skip to content

WebSocket API

License Monitor provides WebSocket endpoints for real-time streaming of logs, metrics, and combined data.

EndpointPurpose
/ws/logsReal-time log streaming
/ws/metricsReal-time metrics streaming
/ws/combinedCombined logs and metrics
ws://hostname:8080/ws/logs?api_key=YOUR_API_KEY
wss://hostname/ws/logs?api_key=YOUR_API_KEY # With TLS

Authentication is required for WebSocket connections:

// Query parameter (recommended for WebSocket)
const ws = new WebSocket('ws://localhost:8080/ws/logs?api_key=YOUR_API_KEY');
// Or with protocol header
const ws = new WebSocket('ws://localhost:8080/ws/logs', ['api_key', 'YOUR_API_KEY']);
const ws = new WebSocket('ws://localhost:8080/ws/logs?api_key=YOUR_API_KEY');
ws.onopen = () => {
console.log('Connected to log stream');
// Subscribe to specific log levels
ws.send(JSON.stringify({
action: 'subscribe',
filter: {
level: ['error', 'warn'],
source: 'license-server'
}
}));
};
ws.onmessage = (event) => {
const log = JSON.parse(event.data);
console.log(`[${log.level}] ${log.message}`);
};
ws.onclose = (event) => {
console.log('Disconnected:', event.code, event.reason);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
{
"type": "log",
"timestamp": "2024-01-15T10:30:45.123Z",
"level": "warn",
"message": "License utilization at 90%",
"source": "license-server",
"metadata": {
"server": "flexlm-01",
"feature": "Maya_Complete",
"utilization": 90
}
}
// Subscribe with filter
ws.send(JSON.stringify({
action: 'subscribe',
filter: {
level: ['error', 'warn'], // Log levels to include
source: 'license-server', // Source filter
pattern: 'utilization' // Message pattern match
}
}));
// Update filter
ws.send(JSON.stringify({
action: 'filter',
filter: {
level: ['error']
}
}));
// Clear filter (receive all logs)
ws.send(JSON.stringify({
action: 'filter',
filter: null
}));
const ws = new WebSocket('ws://localhost:8080/ws/metrics?api_key=YOUR_API_KEY');
ws.onopen = () => {
// Configure metrics interval
ws.send(JSON.stringify({
action: 'configure',
interval: 5000 // Receive metrics every 5 seconds
}));
};
ws.onmessage = (event) => {
const metrics = JSON.parse(event.data);
updateDashboard(metrics);
};
{
"type": "metrics",
"timestamp": "2024-01-15T10:30:45.123Z",
"system": {
"cpu": {
"usage": 34.5,
"cores": 4
},
"memory": {
"total": 8589934592,
"used": 4294967296,
"available": 4294967296,
"percentage": 50.0
},
"disk": {
"total": 107374182400,
"used": 53687091200,
"available": 53687091200,
"percentage": 50.0
}
},
"licenses": {
"total": 100,
"used": 75,
"available": 25,
"utilization": 75.0
},
"connections": {
"active": 12,
"total": 156
}
}
const ws = new WebSocket('ws://localhost:8080/ws/combined?api_key=YOUR_API_KEY');
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
switch (message.type) {
case 'log':
handleLog(message);
break;
case 'metrics':
handleMetrics(message);
break;
case 'alert':
handleAlert(message);
break;
}
};
ActionDescriptionPayload
subscribeSubscribe with filter{ filter: {...} }
filterUpdate filter{ filter: {...} }
configureConfigure stream{ interval: number }
pingKeep-alive ping{}
TypeDescription
logLog entry
metricsMetrics snapshot
alertAlert notification
pongPing response
errorError message
{
"type": "error",
"code": "INVALID_FILTER",
"message": "Invalid filter configuration",
"timestamp": "2024-01-15T10:30:45.123Z"
}
// Send ping every 30 seconds
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ action: 'ping' }));
}
}, 30000);
// Handle pong
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'pong') {
console.log('Connection alive');
}
};

The server closes idle connections after 5 minutes without activity. Send periodic pings to keep the connection alive.

class ReconnectingWebSocket {
constructor(url, options = {}) {
this.url = url;
this.maxRetries = options.maxRetries || 10;
this.baseDelay = options.baseDelay || 1000;
this.maxDelay = options.maxDelay || 30000;
this.retries = 0;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
this.retries = 0;
console.log('Connected');
};
this.ws.onclose = (event) => {
if (event.code !== 1000) {
this.reconnect();
}
};
this.ws.onerror = () => {
// Error will trigger onclose
};
}
reconnect() {
if (this.retries >= this.maxRetries) {
console.error('Max retries exceeded');
return;
}
const delay = Math.min(
this.baseDelay * Math.pow(2, this.retries),
this.maxDelay
);
console.log(`Reconnecting in ${delay}ms...`);
setTimeout(() => {
this.retries++;
this.connect();
}, delay);
}
}
import asyncio
import websockets
import json
async def stream_logs():
uri = "ws://localhost:8080/ws/logs?api_key=YOUR_API_KEY"
async with websockets.connect(uri) as ws:
# Subscribe to error logs
await ws.send(json.dumps({
"action": "subscribe",
"filter": {"level": ["error"]}
}))
async for message in ws:
log = json.loads(message)
print(f"[{log['level']}] {log['message']}")
asyncio.run(stream_logs())
package main
import (
"encoding/json"
"log"
"github.com/gorilla/websocket"
)
func main() {
url := "ws://localhost:8080/ws/logs?api_key=YOUR_API_KEY"
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Fatal("dial:", err)
}
defer conn.Close()
// Subscribe
conn.WriteJSON(map[string]interface{}{
"action": "subscribe",
"filter": map[string]interface{}{
"level": []string{"error", "warn"},
},
})
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}
var logEntry map[string]interface{}
json.Unmarshal(message, &logEntry)
log.Printf("[%s] %s", logEntry["level"], logEntry["message"])
}
}