Roxen.git
/
server
/
etc
/
modules
/
Logger.pmod
version
»
Context lines:
10
20
40
80
file
none
3
Roxen.git/server/etc/modules/Logger.pmod:1:
+
//! Your average base class for loggers. This one outputs by default
+
//! to stderr using the Roxen werror() method.
+
class BaseLogger {
+
object parent_logger;
+
mapping defaults = ([
+
"level" : INFO,
+
]);
-
+
enum {
+
TRACE = 10,
+
DEBUG = 20,
+
INFO = 30,
+
WARN = 40,
+
ERROR = 50,
+
FATAL = 60
+
};
+
+
int default_log_level = INFO;
+
+
//! Override to allow early bailout in @[log()] if noone is
+
//! listening. @[log()] will bail out if this method returns 0.
+
int should_log() {
+
if (parent_logger)
+
return parent_logger->should_log();
+
+
return 1;
+
}
+
+
//! Send the actual log to listeners.
+
//! Override this method to change where log is sent and how it is encoded.
+
protected void do_log(mapping data) {
+
if (!data->level) {
+
data = data | ([]); // Make sure we don't modify the original mapping!
+
data->level = default_log_level;
+
}
+
+
string res = Standards.JSON.encode(data);
+
}
+
+
//! Override this method to implement custom merging of data into
+
//! messages.
+
//!
+
//! The default logic is to just merge the default
+
//! mapping into the message. If more dynamic defaults (such as
+
//! current time etc) is needed, overriding this method is your best
+
//! bet.
+
protected mapping merge_defaults(mapping msg) {
+
return defaults | msg;
+
}
+
+
//! Log an entry using this logger.
+
//!
+
//! If data is a string, it will be converted into a mapping using
+
//! the key "msg" for the message.
+
//!
+
//! Overriding this method should not be needed.
+
void log(mapping|string data) {
+
// Check for early bailout
+
if (!should_log()) {
+
return;
+
}
+
+
if (stringp(data)) {
+
data = ([ "msg" : data ]);
+
}
+
+
mapping log_entry = merge_defaults(data);
+
if (parent_logger && functionp(parent_logger->log)) {
+
parent_logger->log(log_entry);
+
} else {
+
do_log(log_entry);
+
}
+
}
+
+
//! Default parameter mapping and a parent logger object.
+
//!
+
//! The @[parent_logger] object is used to pass any log messages
+
//! injected into this logger up the chain. By default, this logger
+
//! does not log at it's own level if a parent logger is given. Instead,
+
//! it will simply add its defaults and pass the complete log entry up
+
//! to the parent which is then responsible for handling the actual logging.
+
void create(void|mapping|function defaults, void|object parent_logger) {
+
this_program::defaults = defaults || ([]);
+
this_program::parent_logger = parent_logger;
+
}
+
+
this_program child(void|mapping|function defaults) {
+
this_program new_logger = object_program(this)(defaults, this);
+
return new_logger;
+
}
+
}
+
+
// Output to all listeners on a Unix socket somewhere.
+
//
+
// Note: Any object using this class should have unbind_all() called
+
// before the end of its life cycle to avoid leaving socket files on
+
// disk. destroy() will also take care of this, but isn't always
+
// called on cleanup.
+
class SocketLogger {
+
inherit Logger.BaseLogger;
+
+
multiset(object) listeners = (<>);
+
array(object) ports;
+
mapping(string:object) ports_by_path = ([]);
+
+
class LogClient {
+
Stdio.File sock;
+
Stdio.Buffer output = Stdio.Buffer(4096);
+
+
// Write data to the log buffer (output) and set up the write
+
// callback, which will add the data to the actual stream.
+
int write(string|Stdio.Buffer data) {
+
if (stringp(data)) {
+
data = Stdio.Buffer(data);
+
}
+
+
output->add(data);
+
sock->set_write_callback(write_cb);
+
}
+
+
protected int write_cb(mixed self, Stdio.Buffer buff) {
+
int res = 0;
+
+
// Assuming we have data in this Logger's output buffer, we add
+
// that output buffer to the socket buffer and create a new
+
// buffer for future messages to this logger.
+
if (sizeof(output)) {
+
buff->add(output);
+
res = sizeof(output);
+
output->clear();
+
// output = Stdio.Buffer(4096);
+
}
+
+
if (!sizeof(buff)) {
+
sock->set_write_callback(0);
+
}
+
+
return res;
+
}
+
+
// Clean up when closed
+
protected void close_cb(mixed self) {
+
listeners[self] = 0;
+
object f = self->sock;
+
f->set_nonblocking(0,0,0);
+
f->set_id(0);
+
f->close();
+
+
destruct(f);
+
}
+
+
protected void read_cb(mixed self, Stdio.Buffer buf) {
+
// Dummy method - we don't care what the client is saying.
+
// This is needed to ensure our close_cb is called properly when the client disconnects.
+
buf->clear();
+
}
+
+
void create(Stdio.File f) {
+
sock = f;
+
f->set_id(this);
+
f->set_buffer_mode(Stdio.Buffer(1024), Stdio.Buffer(4096)); // Arbitrary buffer length - may need to be adjusted.
+
f->set_nonblocking(read_cb, 0, close_cb);
+
}
+
};
+
+
int should_log() {
+
// If noone is listening there is no point in doing any work.
+
return sizeof(listeners);
+
}
+
+
void do_log(mapping entry) {
+
string res = Standards.JSON.encode(entry);
+
Stdio.Buffer tmp = Stdio.Buffer(res);
+
tmp->add("\n"); // Add a newline so that the reciever can parse data based on lines.
+
sizeof(listeners) && indices(listeners)->write(tmp);
+
}
+
+
void accept_cb(object port) {
+
object l = port->accept();
+
l = LogClient(l);
+
listeners[l] = 1;
+
}
+
+
array get_bound_ports() {
+
return indices(ports_by_path);
+
}
+
+
// Binds a port for the specified socket path
+
object bind(string socket_path) {
+
if (ports_by_path[socket_path]) {
+
werror("Port already bound for logger!\n");
+
return UNDEFINED;
+
}
+
+
Stdio.Port port = Stdio.Port();
+
port->set_id(port);
+
if (search(socket_path, ":") == -1) {
+
// No port specified - assume UNIX socket
+
int res = port->bind_unix(socket_path, accept_cb);
+
if (!res) {
+
werror("Failed to bind socket path %O for configuration logger.\n", socket_path);
+
return UNDEFINED;
+
} else {
+
ports_by_path[socket_path] = port;
+
return port;
+
}
+
} else {
+
sscanf(socket_path, "%s:%d", string ip, int tcp_port);
+
switch(ip||"") {
+
case "":
+
case "*":
+
ip = "::";
+
default:
+
}
+
+
int res = port->bind(tcp_port, accept_cb, ip, 1);
+
if (res != 1) {
+
werror("Failed to bind log port!\n%s\n", strerror(port->errno()));
+
} else {
+
ports_by_path[socket_path] = port;
+
return port;
+
}
+
}
+
}
+
+
// Unbinds the specified path or port object. Returns 1 on success and 0 on failure.
+
int unbind(string|object socket) {
+
if (stringp(socket)) {
+
socket = ports_by_path[socket];
+
}
+
+
if (!objectp(socket)) {
+
werror("Invalid log socket used in unbind request.\n");
+
return 0;
+
}
+
+
foreach(ports_by_path; string path; Stdio.Port p) {
+
if (p == socket) {
+
p->close();
+
if (Stdio.exist(path)) {
+
if (!rm(path)) {
+
werror("Failed to remove log socket %s\n", path);
+
}
+
}
+
m_delete(ports_by_path, path);
+
destruct(p);
+
return 1;
+
}
+
}
+
+
return 0;
+
}
+
+
// Unbind all bound sockets by this logger
+
void unbind_all() {
+
foreach(ports_by_path; string path; object port) {
+
unbind(port);
+
}
+
}
+
+
void create(void|mapping defaults, void|object parent_logger, void|string socket_path) {
+
::create(defaults, parent_logger);
+
+
if (socket_path) {
+
bind(socket_path);
+
}
+
}
+
+
void destroy() {
+
unbind_all();
+
}
+
}
+
+
class MainLogger {
+
inherit Logger.SocketLogger;
+
+
mapping merge_defaults(mapping msg) {
+
mapping tmp = ([
+
"time" : time(),
+
]);
+
+
return ::merge_defaults(msg) | tmp;
+
}
+
+
void create() {
+
::create(0,0,"*:7702");
+
}
+
}
Newline at end of file added.