Changes to RPC object logging and Node config
- Move config from Python to Node - Also log responses - Only log request/response object properties in Node, as logging them in both Node and Python is redundant - Error-out if Node listen config is missing - For convenience, make PeerClient copy setting properties from ClientManager instead of referencing them
This commit is contained in:
parent
454d1b72cc
commit
27b2c15ad3
|
@ -117,7 +117,6 @@ class Config(BaseBridgeConfig):
|
||||||
else:
|
else:
|
||||||
copy("rpc.connection.host")
|
copy("rpc.connection.host")
|
||||||
copy("rpc.connection.port")
|
copy("rpc.connection.port")
|
||||||
copy("rpc.logging_keys")
|
|
||||||
|
|
||||||
def _get_permissions(self, key: str) -> tuple[bool, bool, bool, str]:
|
def _get_permissions(self, key: str) -> tuple[bool, bool, bool, str]:
|
||||||
level = self["bridge.permissions"].get(key, "")
|
level = self["bridge.permissions"].get(key, "")
|
||||||
|
|
|
@ -252,11 +252,6 @@ rpc:
|
||||||
# Only for type: tcp
|
# Only for type: tcp
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 29392
|
port: 29392
|
||||||
# Command arguments to print in logs. Optional.
|
|
||||||
# TODO Support nested arguments, like channel_props.ktid
|
|
||||||
logging_keys:
|
|
||||||
- mxid
|
|
||||||
#- channel_props
|
|
||||||
|
|
||||||
# Python logging configuration.
|
# Python logging configuration.
|
||||||
#
|
#
|
||||||
|
|
|
@ -86,7 +86,6 @@ class RPCClient:
|
||||||
_is_connected: CancelableEvent
|
_is_connected: CancelableEvent
|
||||||
_is_disconnected: CancelableEvent
|
_is_disconnected: CancelableEvent
|
||||||
_connection_lock: asyncio.Lock
|
_connection_lock: asyncio.Lock
|
||||||
_logging_keys: list[str]
|
|
||||||
|
|
||||||
def __init__(self, config: Config, register_config_key: str) -> None:
|
def __init__(self, config: Config, register_config_key: str) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
|
@ -106,7 +105,6 @@ class RPCClient:
|
||||||
self._is_disconnected = CancelableEvent(self.loop)
|
self._is_disconnected = CancelableEvent(self.loop)
|
||||||
self._is_disconnected.set()
|
self._is_disconnected.set()
|
||||||
self._connection_lock = asyncio.Lock()
|
self._connection_lock = asyncio.Lock()
|
||||||
self._logging_keys = config["rpc.logging_keys"]
|
|
||||||
|
|
||||||
async def connect(self) -> None:
|
async def connect(self) -> None:
|
||||||
async with self._connection_lock:
|
async with self._connection_lock:
|
||||||
|
@ -149,8 +147,7 @@ class RPCClient:
|
||||||
self._read_task = self.loop.create_task(self._try_read_loop())
|
self._read_task = self.loop.create_task(self._try_read_loop())
|
||||||
await self._raw_request("register",
|
await self._raw_request("register",
|
||||||
peer_id=self.config["appservice.address"],
|
peer_id=self.config["appservice.address"],
|
||||||
register_config=self.config[self.register_config_key],
|
register_config=self.config[self.register_config_key])
|
||||||
logging_keys=self._logging_keys)
|
|
||||||
self._is_connected.set()
|
self._is_connected.set()
|
||||||
self._is_disconnected.clear()
|
self._is_disconnected.clear()
|
||||||
|
|
||||||
|
@ -250,6 +247,7 @@ class RPCClient:
|
||||||
self.log.debug(f"Nobody waiting for response to {req_id}")
|
self.log.debug(f"Nobody waiting for response to {req_id}")
|
||||||
return
|
return
|
||||||
if command == "response":
|
if command == "response":
|
||||||
|
self.log.debug("Received response %d", req_id)
|
||||||
waiter.set_result(req.get("response"))
|
waiter.set_result(req.get("response"))
|
||||||
elif command == "error":
|
elif command == "error":
|
||||||
waiter.set_exception(RPCError(req.get("error", line)))
|
waiter.set_exception(RPCError(req.get("error", line)))
|
||||||
|
@ -305,10 +303,7 @@ class RPCClient:
|
||||||
req_id = self._next_req_id
|
req_id = self._next_req_id
|
||||||
future = self._response_waiters[req_id] = self.loop.create_future()
|
future = self._response_waiters[req_id] = self.loop.create_future()
|
||||||
req = {"id": req_id, "command": command, **data}
|
req = {"id": req_id, "command": command, **data}
|
||||||
self.log.debug("Request %d: %s", req_id,
|
self.log.debug("Request %d: %s", req_id, command)
|
||||||
', '.join(
|
|
||||||
[command] +
|
|
||||||
[f"{k}: {data[k]}" for k in self._logging_keys if k in data]))
|
|
||||||
assert self._writer is not None
|
assert self._writer is not None
|
||||||
self._writer.write(json.dumps(req).encode("utf-8"))
|
self._writer.write(json.dumps(req).encode("utf-8"))
|
||||||
self._writer.write(b"\n")
|
self._writer.write(b"\n")
|
||||||
|
|
|
@ -3,5 +3,8 @@ If `type` is `unix`, `path` is the path where to create the socket, and `force`
|
||||||
|
|
||||||
If `type` is `tcp`, `port` and `host` are the host/port where to listen.
|
If `type` is `tcp`, `port` and `host` are the host/port where to listen.
|
||||||
|
|
||||||
### Register timeout
|
### Register timeout config
|
||||||
`register_timeout` is the amount of time (in milliseconds) that a connecting peer must send a "register" command after initiating a connection.
|
`register_timeout` is the amount of time (in milliseconds) that a connecting peer must send a "register" command after initiating a connection.
|
||||||
|
|
||||||
|
### Logging config
|
||||||
|
`logging_keys` specifies which properties of RPC request & response objects to print in logs. Optional.
|
||||||
|
|
|
@ -3,5 +3,10 @@
|
||||||
"type": "unix",
|
"type": "unix",
|
||||||
"path": "/data/rpc.sock",
|
"path": "/data/rpc.sock",
|
||||||
"force": true
|
"force": true
|
||||||
|
},
|
||||||
|
"register_timeout": 3000,
|
||||||
|
"logging_keys": {
|
||||||
|
"request": ["mxid"],
|
||||||
|
"response": ["status"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,5 +4,9 @@
|
||||||
"path": "/var/run/matrix-appservice-kakaotalk/rpc.sock",
|
"path": "/var/run/matrix-appservice-kakaotalk/rpc.sock",
|
||||||
"force": false
|
"force": false
|
||||||
},
|
},
|
||||||
"register_timeout": 3000
|
"register_timeout": 3000,
|
||||||
|
"logging_keys": {
|
||||||
|
"request": ["mxid"],
|
||||||
|
"response": ["status"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -425,6 +425,9 @@ export default class PeerClient {
|
||||||
*/
|
*/
|
||||||
constructor(manager, socket, connID) {
|
constructor(manager, socket, connID) {
|
||||||
this.manager = manager
|
this.manager = manager
|
||||||
|
this.registerTimeout = manager.registerTimeout
|
||||||
|
this.loggingKeys = manager.loggingKeys
|
||||||
|
|
||||||
this.socket = socket
|
this.socket = socket
|
||||||
this.connID = connID
|
this.connID = connID
|
||||||
|
|
||||||
|
@ -433,8 +436,6 @@ export default class PeerClient {
|
||||||
this.maxCommandID = 0
|
this.maxCommandID = 0
|
||||||
this.peerID = ""
|
this.peerID = ""
|
||||||
this.deviceName = "KakaoTalk Bridge"
|
this.deviceName = "KakaoTalk Bridge"
|
||||||
/** @type {[string]} */
|
|
||||||
this.loggingKeys = []
|
|
||||||
/** @type {Map<string, UserClient>} */
|
/** @type {Map<string, UserClient>} */
|
||||||
this.userClients = new Map()
|
this.userClients = new Map()
|
||||||
}
|
}
|
||||||
|
@ -465,10 +466,10 @@ export default class PeerClient {
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!this.peerID && !this.stopped) {
|
if (!this.peerID && !this.stopped) {
|
||||||
this.log(`Didn't receive register request within ${this.manager.registerTimeout/1000} seconds, terminating`)
|
this.log(`Didn't receive register request within ${this.registerTimeout/1000} seconds, terminating`)
|
||||||
this.stop("Register request timeout")
|
this.stop("Register request timeout")
|
||||||
}
|
}
|
||||||
}, this.manager.registerTimeout)
|
}, this.registerTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
async stop(error = null) {
|
async stop(error = null) {
|
||||||
|
@ -1194,12 +1195,10 @@ export default class PeerClient {
|
||||||
* @param {string} req.peer_id
|
* @param {string} req.peer_id
|
||||||
* @param {Object} req.register_config
|
* @param {Object} req.register_config
|
||||||
* @param {string} req.register_config.device_name
|
* @param {string} req.register_config.device_name
|
||||||
* @param {?[string]} req.logging_keys
|
|
||||||
*/
|
*/
|
||||||
handleRegister = async (req) => {
|
handleRegister = async (req) => {
|
||||||
this.peerID = req.peer_id
|
this.peerID = req.peer_id
|
||||||
this.deviceName = req.register_config.device_name || this.deviceName
|
this.deviceName = req.register_config.device_name || this.deviceName
|
||||||
this.loggingKeys = req.logging_keys || this.loggingKeys
|
|
||||||
this.log(`Registered socket ${this.connID} -> ${this.peerID}`)
|
this.log(`Registered socket ${this.connID} -> ${this.peerID}`)
|
||||||
if (this.manager.clients.has(this.peerID)) {
|
if (this.manager.clients.has(this.peerID)) {
|
||||||
const oldClient = this.manager.clients.get(this.peerID)
|
const oldClient = this.manager.clients.get(this.peerID)
|
||||||
|
@ -1230,12 +1229,7 @@ export default class PeerClient {
|
||||||
this.log("Ignoring old request", req.id)
|
this.log("Ignoring old request", req.id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.log(
|
this.log(`Request ${req.id}:`, this.#logObj(req, req.command, this.loggingKeys.request))
|
||||||
`Request ${req.id}:`,
|
|
||||||
[req.command].concat(
|
|
||||||
this.loggingKeys.filter(k => k in req).map(k => `${k}: ${JSON.stringify(req[k], this.#writeReplacer)}`))
|
|
||||||
.join(', ')
|
|
||||||
)
|
|
||||||
this.maxCommandID = req.id
|
this.maxCommandID = req.id
|
||||||
let handler
|
let handler
|
||||||
if (!this.peerID) {
|
if (!this.peerID) {
|
||||||
|
@ -1306,17 +1300,40 @@ export default class PeerClient {
|
||||||
resp.command = "error"
|
resp.command = "error"
|
||||||
if (err instanceof ProtocolError) {
|
if (err instanceof ProtocolError) {
|
||||||
resp.error = err.message
|
resp.error = err.message
|
||||||
|
this.error(`Response ${resp.id}: ${err.message}`)
|
||||||
} else {
|
} else {
|
||||||
resp.error = err.toString()
|
resp.error = err.toString()
|
||||||
this.log(`Error handling request ${resp.id} ${err.stack}`)
|
this.error(`Response ${resp.id}: ${err.stack}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO Check if session is broken. If it is, close the PeerClient
|
if (resp.response) {
|
||||||
|
const success = resp.response.success !== false
|
||||||
|
const logger = (success ? this.log : this.error).bind(this)
|
||||||
|
logger(
|
||||||
|
`Response ${resp.id}:`,
|
||||||
|
this.#logObj(
|
||||||
|
resp.response,
|
||||||
|
success ? "success" : "failure",
|
||||||
|
this.loggingKeys.response
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
await this.write(resp)
|
await this.write(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
#writeReplacer = function(key, value) {
|
/**
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string} desc
|
||||||
|
* @param {[string]} keys
|
||||||
|
*/
|
||||||
|
#logObj(obj, desc, keys) {
|
||||||
|
return [desc].concat(
|
||||||
|
keys.filter(key => key in obj).map(key => `${key}: ${JSON.stringify(obj[key], this.#writeReplacer)}`)
|
||||||
|
).join(', ')
|
||||||
|
}
|
||||||
|
|
||||||
|
#writeReplacer(key, value) {
|
||||||
if (value instanceof Long) {
|
if (value instanceof Long) {
|
||||||
return value.toString()
|
return value.toString()
|
||||||
} else {
|
} else {
|
||||||
|
@ -1324,7 +1341,7 @@ export default class PeerClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#readReviver = function(key, value) {
|
#readReviver(key, value) {
|
||||||
if (value instanceof Object) {
|
if (value instanceof Object) {
|
||||||
// TODO Use a type map if there will be many possible types
|
// TODO Use a type map if there will be many possible types
|
||||||
if (value.__type__ == "Long") {
|
if (value.__type__ == "Long") {
|
||||||
|
|
|
@ -20,11 +20,33 @@ import path from "path"
|
||||||
import PeerClient from "./client.js"
|
import PeerClient from "./client.js"
|
||||||
import { promisify } from "./util.js"
|
import { promisify } from "./util.js"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} ListenConfig
|
||||||
|
* @property {string} type
|
||||||
|
* @property {string} path
|
||||||
|
* @property {boolean} force
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} LoggingKeys
|
||||||
|
* @property {[string]} request
|
||||||
|
* @property {[string]} response
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
export default class ClientManager {
|
export default class ClientManager {
|
||||||
constructor(listenConfig, registerTimeout) {
|
/**
|
||||||
|
* @param {ListenConfig} listenConfig
|
||||||
|
* @param {number} registerTimeout
|
||||||
|
* @param {?LoggingKeys} loggingKeys
|
||||||
|
*/
|
||||||
|
constructor(listenConfig, registerTimeout, loggingKeys) {
|
||||||
|
if (!listenConfig) {
|
||||||
|
throw new Error("Listen config missing")
|
||||||
|
}
|
||||||
this.listenConfig = listenConfig
|
this.listenConfig = listenConfig
|
||||||
this.registerTimeout = registerTimeout || 3000
|
this.registerTimeout = registerTimeout || 3000
|
||||||
|
this.loggingKeys = loggingKeys || {request: [], response: []}
|
||||||
this.server = net.createServer(this.acceptConnection)
|
this.server = net.createServer(this.acceptConnection)
|
||||||
this.connections = []
|
this.connections = []
|
||||||
this.clients = new Map()
|
this.clients = new Map()
|
||||||
|
@ -43,7 +65,7 @@ export default class ClientManager {
|
||||||
} else {
|
} else {
|
||||||
const connID = this.connIDSequence++
|
const connID = this.connIDSequence++
|
||||||
this.connections[connID] = sock
|
this.connections[connID] = sock
|
||||||
new PeerClient(this, sock, connID).start()
|
new PeerClient(this, sock, connID, this.loggingKeys).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,11 @@ const configPath = args["--config"] || "config.json"
|
||||||
console.log("[Main] Reading config from", configPath)
|
console.log("[Main] Reading config from", configPath)
|
||||||
const config = JSON.parse(fs.readFileSync(configPath).toString())
|
const config = JSON.parse(fs.readFileSync(configPath).toString())
|
||||||
|
|
||||||
const manager = new ClientManager(config.listen, config.register_timeout)
|
const manager = new ClientManager(
|
||||||
|
config.listen,
|
||||||
|
config.register_timeout,
|
||||||
|
config.logging_keys
|
||||||
|
)
|
||||||
|
|
||||||
function stop() {
|
function stop() {
|
||||||
manager.stop().then(() => {
|
manager.stop().then(() => {
|
||||||
|
|
Loading…
Reference in New Issue