Overview | Index by: file name |
procedure name |
procedure call |
annotation
log.tcl
(annotations | original source)
#
# Copyright (C) 2010 Alexandros Stergiakis <alsterg@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#//#
# Library for sending and receiving formatted messages to syslog.
#//#
namespace eval log {
namespace export severity2str severity2num getLogs clearLogs mqsync \
Debug Info Notice Warning Error Critical Alert Emergency
namespace import ::session::user_sessions
# Handler for the syslog message queue. It will be notified whenever syslogd writes something to syslog.
# It then dispatches the logs to appropriate application-specific handlers, as registered via syslog_monitor.
#
# @param queue The message queue name.
# @param command The command that corresponds to the syslog message queue.
proc syslog_mq_hdlr {queue command} {
Global BUFFERSIZE_DEF
global booting
variable buffer
while {1} {
set msgs [$command receive]
foreach msg $msgs {
set index [string first | $msg]
set opts [string range $msg 0 $index-2]
set text [string range $msg $index+2 end]
unset index
set secs [lindex $opts 0]
set hostname [lindex $opts 1]
set opts [lindex $opts 2]
lassign [split $opts {.}] facility severity
unset opts
# XXX Ident :
# XXX Pid []
lappend buffer [dict create Time $secs Facility $facility Hostname $hostname Severity $severity Message $text]
if {[llength $buffer] > $BUFFERSIZE_DEF} {
set buffer [lreplace $buffer 0 0] ;# remove first.
}
# Print to terminal(s).
if {! $booting} {
foreach sid [user_sessions] {
Session $sid LOGTHREAS MONITOR
if {$MONITOR && [severity2num $severity] >= $LOGTHREAS} {
Session $sid SYNCH STDOUT2
if {$SYNCH} {
thread::send -async $sid [list pushbuf "$severity: $text"]
} else {
# Note: Since Session's event loop doesn't block, using thread::send -async,
# would also do the job. But the following is even better, as it prints directly on
# the communication pipe and doesn't go through Session's event loop at all.
puts $STDOUT2 "$severity: $text"
}
}
}
}
}
if {[$command queue] == 0} { break }
}
}
proc getLogs {args} {
variable buffer
set number [llength $buffer]
for {set i 0} {$i < [llength $args]} {incr i} {
switch -exact -- [lindex $args $i] {
"-number" {set number [lindex $args [incr i]]}
"-severity" {#XXX}
"-facility" {#XXX}
"-ident" {#XXX}
"-regexp" {#XXX}
"-match" {#XXX}
default { error "unrecognized parameter" }
}
}
set result [list]
foreach e [lreverse $buffer] {
lappend result $e
incr number -1
if {$number == 0} { break }
}
return $result
}
proc clearLogs {} {
variable buffer [list]
}
# Register a proc to be called whenever a particular application logs something to syslog.
# Whatever was logged will be passed to the handler in a list (one list item - one line of log)
#
# @param prog The name of the application/daemon/program to match
# @param handler The procedure (handler) to call when a match succeeds
proc syslog_monitor {prog handler} {
# XXX
variable Monitors
set Monitors($prog) $handler
}
# Convert a severity expressed as a number 0-7, to the equivalent string.
#
# @param num The symbolic number for a severity level.
# @return The corresponding string describing the severity level.
# @error
proc severity2str {num} {
if {! (0 <= $num <= 7)} {
error "severity number out of bounds"
}
return [lindex [list debug info notice warning error critical alert emergency] $num]
}
# Convert a severity expressed as a number 0-7, to the equivalent string.
#
# @param num The symbolic number for a severity level.
# @return The corresponding string describing the severity level.
# @error
proc severity2num {sev} {
set result [lsearch -exact [list debug info notice warning error critical alert emergency] $sev]
if {$result == -1} {
error "unrecognized severity level \"$sev\""
}
return $result
}
# It formats a message and logs it to syslog.
# If debugging is enabled, then together with the message, the originating procedure,
# the namespace and thread id are logged as well to facilitate debugging.
#
# The commands has the following syntax:
# log severity ?options? message-part1 message-part2 ...
# "severity" must be one of:
# emergency: system is unusable
# alert: action must be taken immediately
# critical: critical conditions
# error: error conditions
# warning: warning conditions
# notice: normal but significant condition
# info: informational messages
# debug: debug-level messages
# Case is not significant.
#
# It supports the following options:
# -error : Generate a Tcl error as well, with message the concatenated message-parts.
# -exit <errcode> : Exit after logging with specified error code.
# -stack <errorInfo> : Stack error trace. Like in the ::errorInfo Tcl variable.
# -code <errorCode> : Error code. Like in the ::errorCode Tcl variable.
# -return : Cause the calling function to return $msg after log has been executed. Useful in order to avoid having the same message in two different lines.
# -session <sessionId> : The session from which this log request originated. It can be left unspecified.
# -category <category> : A string to help categorize messages within the context of MikroConf. It can be left unspecified.
# -- : Optional. Signifies the end of the arguments and that the following arguments will be the severity, and all the rest will be the message-parts
#
# @param severity See description above.
# @param args See description above.
# @error
proc log {severity args} {
global booting
# Default values
array set Param {
error 0
exit 0
exitcode 0
return 0
session {Main}
category {None}
stack {}
code {}
}
# Parse provided switches. They overwrite the above defaults.
for {set i 0} {$i < [llength $args]} {incr i} {
switch -exact -- [lindex $args $i] {
"-session" {set Param(session) [lindex $args [incr i]]}
"-category" {set Param(category) [lindex $args [incr i]]}
"-error" {set Param(error) 1}
"-exit" {
set Param(exit) 1
set Param(exitcode) [lindex $args [incr i]]
}
"-stack" {set Param(stack) [lindex $args [incr i]]}
"-code" {set Param(code) [lindex $args [incr i]]}
"-return" {set Param(return) 1}
"--" {
incr i
break
}
default {
break
}
}
}
set msg [join [lrange $args $i end]]
syslog -ident "MikroConf" -facility "daemon" $severity $msg
# Print to console.
if {$booting && $severity ne "debug"} {
puts "$severity: $msg"
}
# Print error trace to stderr, along with other info when in Debugging mode.
if {[tsv::get conf DEBUGGING]} {
puts ""
puts "Severity: $severity"
puts "Message: $msg"
# Determine the originating session
puts "Session: $Param(session)"
# Determine the originating namespace
puts "Namespace: [uplevel 2 [list namespace current]]"
# Determine the originating procedure
if {[info level] > 2} {
puts "Procedure: [lindex [info level -2] 0]"
} else {
puts "Procedure: main()"
}
puts "Category: $Param(category)"
if {! [lempty $Param(stack)]} {
puts stderr "Stack trace:\n$Param(stack)"
}
if {! [lempty $Param(code)]} {
puts stderr "Error code:\n$Param(code)"
}
}
if {$Param(error)} {
if {! [lempty $Param(stack)]} {
return -code error -errorinfo $Param(stack) $msg
}
return -code error $msg
}
if {$Param(return)} {
return -code return $msg2
}
if {$Param(exit)} {
exit $Param(exitcode)
}
return
}
proc Debug {args} { log debug {*}$args }
proc Info {args} { log info {*}$args }
proc Notice {args} { log notice {*}$args }
proc Warning {args} { log warning {*}$args }
proc Error {args} { log error {*}$args }
proc Critical {args} { log critical {*}$args }
proc Alert {args} { log alert {*}$args }
proc Emergency {args} { log emergency {*}$args }
# Force processing of buffered messages.
proc mqsync {} {
if {[syslog_mq_cmd queue]} { syslog_mq_hdlr "/syslog" "syslog_mq_cmd" }
}
# /syslog = The hardcoded in busybox mqueue path.
# 777 = Give adequate permissions to /syslog so that syslogd can write on it.
# 640 = MAX_READ*2+128 (See busybox syslogd/syslogd.c sources for rationale)
# 200 = 200 * 640 = 64000 bytes = 64KBs
mq open /syslog ::log::syslog_mq_cmd 200 640 777 ::log::syslog_mq_hdlr ;# (@magic-number)
mqsync
} ;# End of Namespace
Overview | Index by: file name |
procedure name |
procedure call |
annotation
File generated 2010-03-13 at 22:28.