JSON-RPC: add log.subscribe and log.unsubscribe

https://www.unrealircd.org/docs/JSON-RPC:Log
This commit is contained in:
Bram Matthys 2023-04-08 17:20:50 +02:00
parent 4945ac9f7e
commit 7c22f37a9f
No known key found for this signature in database
GPG key ID: BF8116B163EAAE98
10 changed files with 175 additions and 18 deletions

View file

@ -58,6 +58,11 @@ You can help us by testing this release and reporting any issues at https://bugs
and [User](https://www.unrealircd.org/docs/JSON-RPC:User#Structure_of_a_client_object)
response object will be. Especially useful if you don't need all the
details in the list calls.
* New JSON-RPC methods
[`log.subscribe`](https://www.unrealircd.org/docs/JSON-RPC:Log#log.subscribe) and
[`log.unsubscribe`](https://www.unrealircd.org/docs/JSON-RPC:Log#log.unsubscribe)
to allow real-time streaming of
[JSON log events](https://www.unrealircd.org/docs/JSON_logging).
* New JSON-RPC method
[`rpc.set_issuer`](https://www.unrealircd.org/docs/JSON-RPC:Rpc#rpc.set_issuer)
to indiciate who is actually issuing the requests. The admin panel uses this

View file

@ -249,6 +249,7 @@ loadmodule "rpc/server_ban";
loadmodule "rpc/server_ban_exception";
loadmodule "rpc/name_ban";
loadmodule "rpc/spamfilter";
loadmodule "rpc/log";
/*** Other ***/
// These are modules that don't fit in any of the previous sections

View file

@ -1268,7 +1268,7 @@ extern const char *log_type_valtostring(LogType v);
#endif
extern void do_unreal_log(LogLevel loglevel, const char *subsystem, const char *event_id, Client *client, const char *msg, ...) __attribute__((format(printf,5,0)));
extern void do_unreal_log_raw(LogLevel loglevel, const char *subsystem, const char *event_id, Client *client, const char *msg, ...);
extern void do_unreal_log_internal_from_remote(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, Client *from_server);
extern void do_unreal_log_internal_from_remote(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, Client *from_server);
extern LogData *log_data_string(const char *key, const char *str);
extern LogData *log_data_char(const char *key, const char c);
extern LogData *log_data_integer(const char *key, int64_t integer);
@ -1291,6 +1291,9 @@ extern const char *log_level_valtostring(LogLevel loglevel);
extern LogLevel log_level_stringtoval(const char *str);
extern int valid_event_id(const char *s);
extern int valid_subsystem(const char *s);
extern LogSource *add_log_source(const char *str);
extern void free_log_sources(LogSource *l);
extern int log_sources_match(LogSource *logsource, LogLevel loglevel, const char *subsystem, const char *event_id, int matched_already);
extern const char *timestamp_iso8601_now(void);
extern const char *timestamp_iso8601(time_t v);
extern int is_valid_snomask(char c);

View file

@ -1839,11 +1839,12 @@ int hooktype_tkl_del(Client *client, TKL *tkl);
* @param subsystem Subsystem (eg "operoverride")
* @param event_id Event ID (eg "SAJOIN_COMMAND")
* @param msg Message(s) in text form
* @param json_serialized The associated JSON text
* @param json The JSON log entry
* @param json_serialized The serialized JSON log entry (as a string)
* @param timebuf The [xxxx] time buffer, for convenience
* @return The return value is ignored (use return 0)
*/
int hooktype_log(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, const char *timebuf);
int hooktype_log(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, const char *timebuf);
/** Called when a local user matches a spamfilter (function prototype for HOOKTYPE_LOCAL_SPAMFILTER).
* @param client The client

View file

@ -1443,6 +1443,7 @@ struct RPCClient {
char *rpc_user; /**< Name of the rpc-user block after authentication, NULL during pre-auth */
char *issuer; /**< Optional name of the issuer, set by rpc.set_issuer(), eg logged in user on admin panel, can be NULL */
json_t *rehash_request; /**< If a REHASH (request) is currently running, otherwise NULL */
LogSource *log_sources; /**< Subscribed to which log sources */
};
struct MessageTag {

View file

@ -147,6 +147,19 @@ Client *make_client(Client *from, Client *servr)
return client;
}
/** Free the client->rpc struct.
* NOTE: if you want to fully free the entire client, call free_client()
*/
void free_client_rpc(Client *client)
{
safe_free(client->rpc->rpc_user);
safe_free(client->rpc->issuer);
if (client->rpc->rehash_request)
json_decref(client->rpc->rehash_request);
free_log_sources(client->rpc->log_sources);
safe_free(client->rpc);
}
void free_client(Client *client)
{
if (!list_empty(&client->client_node))
@ -191,15 +204,10 @@ void free_client(Client *client)
del_from_id_hash_table(client->id, client);
}
}
if (client->rpc)
{
safe_free(client->rpc->rpc_user);
safe_free(client->rpc->issuer);
if (client->rpc->rehash_request)
json_decref(client->rpc->rehash_request);
safe_free(client->rpc);
}
free_client_rpc(client);
safe_free(client->ip);
mp_pool_release(client);

View file

@ -880,7 +880,7 @@ literal:
}
/** Do the actual writing to log files */
void do_unreal_log_disk(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, Client *from_server)
void do_unreal_log_disk(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, Client *from_server)
{
static time_t last_log_file_warning = 0;
Log *l;
@ -893,7 +893,7 @@ void do_unreal_log_disk(LogLevel loglevel, const char *subsystem, const char *ev
snprintf(timebuf, sizeof(timebuf), "[%s] ", myctime(TStime()));
RunHook(HOOKTYPE_LOG, loglevel, subsystem, event_id, msg, json_serialized, timebuf);
RunHook(HOOKTYPE_LOG, loglevel, subsystem, event_id, msg, json, json_serialized, timebuf);
if (!loop.forked && (loglevel > ULOG_DEBUG))
{
@ -1535,7 +1535,7 @@ void do_unreal_log_internal(LogLevel loglevel, const char *subsystem, const char
/* Now call all the loggers: */
do_unreal_log_disk(loglevel, subsystem, event_id, mmsg, json_serialized, from_server);
do_unreal_log_disk(loglevel, subsystem, event_id, mmsg, j, json_serialized, from_server);
if ((loop.rehashing == 2) || !strcmp(subsystem, "config"))
do_unreal_log_control(loglevel, subsystem, event_id, mmsg, j, json_serialized, from_server);
@ -1572,14 +1572,14 @@ void do_unreal_log_internal(LogLevel loglevel, const char *subsystem, const char
}
void do_unreal_log_internal_from_remote(LogLevel loglevel, const char *subsystem, const char *event_id,
MultiLine *msg, const char *json_serialized, Client *from_server)
MultiLine *msg, json_t *json, const char *json_serialized, Client *from_server)
{
if (unreal_log_recursion_trap)
return;
unreal_log_recursion_trap = 1;
/* Call the disk loggers */
do_unreal_log_disk(loglevel, subsystem, event_id, msg, json_serialized, from_server);
do_unreal_log_disk(loglevel, subsystem, event_id, msg, json, json_serialized, from_server);
/* And to IRC */
do_unreal_log_opers(loglevel, subsystem, event_id, msg, json_serialized, from_server);

View file

@ -32,7 +32,9 @@ INCLUDES = ../../include/channel.h \
../../include/version.h ../../include/whowas.h
R_MODULES= \
rpc.so stats.so user.so server.so channel.so server_ban.so server_ban_exception.so name_ban.so spamfilter.so
rpc.so stats.so user.so server.so channel.so server_ban.so \
server_ban_exception.so name_ban.so spamfilter.so \
log.so
MODULES=$(R_MODULES)
MODULEFLAGS=@MODULEFLAGS@

136
src/modules/rpc/log.c Normal file
View file

@ -0,0 +1,136 @@
/* log.* RPC calls
* (C) Copyright 2023-.. Bram Matthys (Syzop) and the UnrealIRCd team
* License: GPLv2 or later
*/
#include "unrealircd.h"
ModuleHeader MOD_HEADER
= {
"rpc/log",
"1.0.0",
"log.* RPC calls",
"UnrealIRCd Team",
"unrealircd-6",
};
/* Forward declarations */
void rpc_log_hook_subscribe(Client *client, json_t *request, json_t *params);
void rpc_log_hook_unsubscribe(Client *client, json_t *request, json_t *params);
int rpc_log_hook(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, const char *timebuf);
MOD_INIT()
{
RPCHandlerInfo r;
MARK_AS_OFFICIAL_MODULE(modinfo);
memset(&r, 0, sizeof(r));
r.method = "log.subscribe";
r.loglevel = ULOG_DEBUG;
r.call = rpc_log_hook_subscribe;
if (!RPCHandlerAdd(modinfo->handle, &r))
{
config_error("[rpc/log] Could not register RPC handler");
return MOD_FAILED;
}
memset(&r, 0, sizeof(r));
r.method = "log.unsubscribe";
r.loglevel = ULOG_DEBUG;
r.call = rpc_log_hook_unsubscribe;
if (!RPCHandlerAdd(modinfo->handle, &r))
{
config_error("[rpc/log] Could not register RPC handler");
return MOD_FAILED;
}
HookAdd(modinfo->handle, HOOKTYPE_LOG, 0, rpc_log_hook);
return MOD_SUCCESS;
}
MOD_LOAD()
{
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
return MOD_SUCCESS;
}
void rpc_log_hook_subscribe(Client *client, json_t *request, json_t *params)
{
json_t *result;
json_t *sources;
size_t index;
json_t *value;
const char *str;
LogSource *s;
sources = json_object_get(params, "sources");
if (!sources || !json_is_array(sources))
{
rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: '%s'", "sources");
return;
}
/* Erase the old subscriptions first */
free_log_sources(client->rpc->log_sources);
client->rpc->log_sources = NULL;
/* Add subscriptions... */
json_array_foreach(sources, index, value)
{
str = json_get_value(value);
if (!str)
continue;
s = add_log_source(str);
AddListItem(s, client->rpc->log_sources);
}
result = json_boolean(1);
rpc_response(client, request, result);
json_decref(result);
}
/** log.unsubscribe: unsubscribe from all log messages */
void rpc_log_hook_unsubscribe(Client *client, json_t *request, json_t *params)
{
json_t *result;
free_log_sources(client->rpc->log_sources);
client->rpc->log_sources = NULL;
result = json_boolean(1);
rpc_response(client, request, result);
json_decref(result);
}
int rpc_log_hook(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, const char *timebuf)
{
Client *client;
json_t *request = NULL;
list_for_each_entry(client, &unknown_list, lclient_node)
{
if (IsRPC(client) && client->rpc->log_sources &&
log_sources_match(client->rpc->log_sources, loglevel, subsystem, event_id, 0))
{
if (request == NULL)
{
/* Lazy initalization */
request = json_object();
json_object_set_new(request, "method", json_string_unreal("log.event"));
}
rpc_response(client, request, json);
}
}
if (request)
json_decref(request);
return 0;
}

View file

@ -157,7 +157,7 @@ CMD_FUNC(cmd_slog)
json_serialized = json_dumps(j, JSON_COMPACT);
if (json_serialized)
do_unreal_log_internal_from_remote(loglevel, subsystem, event_id, mmsg, json_serialized, client);
do_unreal_log_internal_from_remote(loglevel, subsystem, event_id, mmsg, j, json_serialized, client);
/* Broadcast to the other servers */
sendto_server(client, 0, 0, recv_mtags, ":%s SLOG %s %s %s :%s",