|
|
class BaseJSONLogger { |
constant BUNYAN_VERSION = 0; |
|
object parent_logger; |
string logger_name = "unknown"; |
string hostname = (functionp(System.gethostname) && System.gethostname()) || "unknown"; |
int pid = System.getpid(); |
|
mapping|function defaults = ([ |
"level" : INFO, |
]); |
|
enum { |
TRACE = 10, |
DBG = 20, |
INFO = 30, |
WARN = 40, |
ERROR = 50, |
FATAL = 60 |
}; |
|
int default_log_level = INFO; |
int drop_messages_below = 0; |
|
|
|
int should_log(string|mapping msg) { |
int level = (stringp(msg) && default_log_level) || msg->level || default_log_level; |
if (level < drop_messages_below) return 0; |
|
if (parent_logger) |
return parent_logger->should_log(msg); |
|
return 1; |
} |
|
|
|
protected void do_log(mapping data) { |
if (!data->level) { |
data = data | ([]); |
data->level = default_log_level; |
} |
|
string res = Standards.JSON.encode(data); |
} |
|
|
|
string get_bunyan_timestamp() { |
array(int) tod; |
#if constant(System.gettimeofday) |
tod = System.gettimeofday(); |
#else |
tod = ({ time(1), 0, 0 }); |
#endif |
mapping gt = gmtime(tod[0]); |
string ret = sprintf("%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", |
1900 + gt->year, 1+gt->mon, gt->mday, |
gt->hour, gt->min, gt->sec, |
tod[1]/1000); |
|
return ret; |
} |
|
|
|
|
|
|
|
|
protected mapping merge_defaults(mapping msg) { |
mapping ret = ((functionp(defaults) && defaults()) || defaults) | msg; |
|
|
ret->v = BUNYAN_VERSION; |
ret->name = ret->name || logger_name; |
ret->hostname = hostname; |
ret->pid = pid; |
ret->time = get_bunyan_timestamp(); |
ret->msg = ret->msg || ""; |
return ret; |
} |
|
|
|
|
|
|
|
void log(mapping|string data) { |
|
if (!should_log(data)) { |
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); |
} |
} |
|
void set_name(void|string name) { |
logger_name = name || "unknown"; |
} |
|
void set_defaults(void|function|mapping new_defaults) { |
defaults = new_defaults || ([]); |
} |
|
|
|
|
|
|
|
|
void create(void|string logger_name, void|mapping|function defaults, void|object parent_logger) { |
set_defaults(defaults); |
this_program::parent_logger = parent_logger; |
set_name(logger_name); |
} |
|
this_program child(string logger_name, void|mapping|function defaults) { |
logger_name = combine_path_unix(this_program::logger_name, logger_name); |
this_program new_logger = object_program(this)(logger_name, defaults, this); |
return new_logger; |
} |
} |
|
|
|
|
|
|
|
class SocketLogger { |
inherit Logger.BaseJSONLogger; |
|
multiset(object) listeners = (<>); |
array(object) ports; |
mapping(string:object) ports_by_path = ([]); |
|
class LogClient { |
Stdio.File sock; |
Stdio.Buffer output = Stdio.Buffer(4096); |
|
|
|
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; |
|
|
|
|
if (sizeof(output)) { |
buff->add(output); |
res = sizeof(output); |
output->clear(); |
|
} |
|
if (!sizeof(buff)) { |
sock->set_write_callback(0); |
} |
|
return res; |
} |
|
|
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) { |
|
|
buf->clear(); |
} |
|
void create(Stdio.File f) { |
sock = f; |
f->set_id(this); |
f->set_buffer_mode(Stdio.Buffer(1024), Stdio.Buffer(4096)); |
f->set_nonblocking(read_cb, 0, close_cb); |
} |
}; |
|
int should_log(string|mapping msg) { |
|
return ::should_log(msg) && sizeof(listeners); |
} |
|
void do_log(mapping entry) { |
string res = Standards.JSON.encode(entry); |
Stdio.Buffer tmp = Stdio.Buffer(res); |
tmp->add("\n"); |
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); |
} |
|
|
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) { |
|
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; |
} |
} |
} |
|
|
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; |
} |
|
|
void unbind_all() { |
foreach(ports_by_path; string path; object port) { |
unbind(port); |
} |
} |
|
|
|
|
|
|
BaseJSONLogger child(string logger_name, void|mapping|function defaults) { |
logger_name = combine_path_unix(this_program::logger_name, logger_name); |
BaseJSONLogger new_logger = BaseJSONLogger(logger_name, defaults, this); |
return new_logger; |
} |
|
void create(string logger_name, void|mapping defaults, void|object parent_logger, void|string socket_path) { |
::create(logger_name, defaults, parent_logger); |
|
if (socket_path) { |
bind(socket_path); |
} |
} |
|
void destroy() { |
unbind_all(); |
} |
} |
|
|